From c8899addfd2deed3d84be2de234681db64987722 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 30 Apr 2007 06:26:38 +0000 Subject: Rearranged the sources a bit. --- COPYRIGHT | 30 - Doxyfile | 275 -- Makefile.am | 178 +- Makefile.in | 638 ++-- PyUnitTests.py | 4 - acprep | 1 - amount.cc | 2041 ----------- amount.h | 760 ----- balance.cc | 313 -- balance.h | 969 ------ binary.cc | 1013 ------ binary.h | 252 -- compile | 142 - configure | 2 +- configure.in | 2 +- context.h | 28 - contrib/ledger.vim | 46 + contrib/ledger.xcodeproj/johnw.mode1 | 1434 ++++++++ contrib/ledger.xcodeproj/johnw.pbxuser | 861 +++++ contrib/ledger.xcodeproj/project.pbxproj | 584 ++++ csv.cc | 0 csv.h | 0 derive.cc | 178 - derive.h | 18 - docs/Doxyfile | 275 ++ docs/ledger.info | 3640 ++++++++++++++++++++ docs/ledger.texi | 3960 ++++++++++++++++++++++ emacs.cc | 0 emacs.h | 0 error.h | 151 - fdstream.hpp | 184 - format.cc | 239 -- format.h | 108 - gdtoa/gd_qnan.h | 12 - gnucash.cc | 366 -- gnucash.h | 75 - journal.cc | 667 ---- journal.h | 471 --- ledger.el | 1256 ------- ledger.h | 43 - ledger.info | 3640 -------------------- ledger.pdf | Bin 282087 -> 0 bytes ledger.texi | 3960 ---------------------- ledger.vim | 46 - ledger.xcodeproj/johnw.mode1 | 1434 -------- ledger.xcodeproj/johnw.pbxuser | 861 ----- ledger.xcodeproj/project.pbxproj | 584 ---- lisp/ledger.el | 1256 +++++++ lisp/timeclock.el | 1362 ++++++++ main.cc | 476 --- mask.cc | 24 - mask.h | 26 - ofx.cc | 218 -- ofx.h | 21 - option.cc | 224 -- option.h | 22 - parser.h | 86 - parsetime.yy | 283 -- py_amount.cc | 242 -- py_balance.cc | 202 -- py_eval.cc | 202 -- py_eval.h | 82 - py_format.cc | 11 - py_journal.cc | 374 -- py_option.cc | 73 - py_parser.cc | 48 - py_report.cc | 13 - py_session.cc | 36 - py_transform.cc | 8 - py_value.cc | 337 -- py_xpath.cc | 79 - pyfstream.h | 138 - pyledger.cc | 10 - pyledger.h | 16 - qif.cc | 243 -- qif.h | 21 - quotes.cc | 80 - quotes.h | 30 - reconcile.cc | 0 reconcile.h | 0 register.cc | 166 - register.h | 38 - report.cc | 189 -- report.h | 141 - scantime.ll | 32 - session.cc | 226 -- session.h | 197 -- setup.py | 4 +- src/.gitignore | 1 + src/COPYRIGHT | 30 + src/amount.cc | 2041 +++++++++++ src/amount.h | 760 +++++ src/balance.cc | 313 ++ src/balance.h | 969 ++++++ src/binary.cc | 1013 ++++++ src/binary.h | 252 ++ src/context.h | 28 + src/csv.cc | 0 src/csv.h | 0 src/derive.cc | 178 + src/derive.h | 18 + src/emacs.cc | 0 src/emacs.h | 0 src/error.h | 151 + src/fdstream.hpp | 184 + src/format.cc | 235 ++ src/format.h | 108 + src/gd_qnan.h | 12 + src/gnucash.cc | 366 ++ src/gnucash.h | 75 + src/journal.cc | 667 ++++ src/journal.h | 471 +++ src/ledger.h | 42 + src/main.cc | 484 +++ src/mask.cc | 24 + src/mask.h | 26 + src/ofx.cc | 218 ++ src/ofx.h | 21 + src/option.cc | 219 ++ src/option.h | 22 + src/parser.h | 86 + src/py_amount.cc | 245 ++ src/py_balance.cc | 202 ++ src/py_format.cc | 11 + src/py_journal.cc | 374 ++ src/py_option.cc | 73 + src/py_parser.cc | 48 + src/py_report.cc | 13 + src/py_session.cc | 36 + src/py_transform.cc | 8 + src/py_value.cc | 337 ++ src/py_xpath.cc | 79 + src/pyfstream.h | 138 + src/pyinterp.cc | 164 + src/pyinterp.h | 83 + src/pyledger.cc | 53 + src/pyledger.h | 16 + src/qif.cc | 243 ++ src/qif.h | 21 + src/quotes.cc | 80 + src/quotes.h | 30 + src/reconcile.cc | 0 src/reconcile.h | 0 src/register.cc | 166 + src/register.h | 38 + src/report.cc | 189 ++ src/report.h | 141 + src/session.cc | 220 ++ src/session.h | 197 ++ src/system.hh | 99 + src/textual.cc | 966 ++++++ src/textual.h | 44 + src/times.cc | 53 + src/times.h | 102 + src/transform.cc | 326 ++ src/transform.h | 136 + src/utils.cc | 687 ++++ src/utils.h | 422 +++ src/value.cc | 2311 +++++++++++++ src/value.h | 580 ++++ src/xml.cc | 550 +++ src/xml.h | 423 +++ src/xmlparse.cc | 467 +++ src/xpath.cc | 2414 +++++++++++++ src/xpath.h | 783 +++++ system.hh | 105 - tests/corelib/numerics/BasicAmount.cc | 625 ---- tests/corelib/numerics/BasicAmount.h | 68 - tests/corelib/numerics/Commodity.cc | 55 - tests/corelib/numerics/Commodity.h | 34 - tests/corelib/numerics/CommodityAmount.cc | 696 ---- tests/corelib/numerics/CommodityAmount.h | 60 - tests/corelib/numerics/DateTime.cc | 79 - tests/corelib/numerics/DateTimeTest.h | 28 - tests/numerics/BasicAmount.cc | 625 ++++ tests/numerics/BasicAmount.h | 68 + tests/numerics/Commodity.cc | 55 + tests/numerics/Commodity.h | 34 + tests/numerics/CommodityAmount.cc | 696 ++++ tests/numerics/CommodityAmount.h | 60 + tests/numerics/DateTime.cc | 79 + tests/numerics/DateTimeTest.h | 28 + tests/python/PyUnitTests.py | 4 + tests/python/UnitTests.py | 4 +- tests/python/corelib/.gitignore | 1 - tests/python/corelib/__init__.py | 0 tests/python/corelib/balances/__init__.py | 0 tests/python/corelib/numerics/.gitignore | 1 - tests/python/corelib/numerics/BasicAmount.py | 527 --- tests/python/corelib/numerics/CommodityAmount.py | 623 ---- tests/python/corelib/numerics/__init__.py | 0 tests/python/corelib/values/__init__.py | 0 tests/python/numerics/.gitignore | 1 + tests/python/numerics/BasicAmount.py | 527 +++ tests/python/numerics/CommodityAmount.py | 623 ++++ tests/python/numerics/__init__.py | 0 tests/python/numerics/balances/__init__.py | 0 tests/python/numerics/values/__init__.py | 0 textual.cc | 966 ------ textual.h | 44 - timeclock.el | 1362 -------- times.cc | 53 - times.h | 102 - transform.cc | 326 -- transform.h | 136 - utils.cc | 687 ---- utils.h | 422 --- value.cc | 2311 ------------- value.h | 580 ---- xml.cc | 550 --- xml.h | 409 --- xmlparse.cc | 465 --- xmlparse.h | 25 - xpath.cc | 2419 ------------- xpath.h | 783 ----- ylwrap | 223 -- 216 files changed, 39229 insertions(+), 39899 deletions(-) delete mode 100644 COPYRIGHT delete mode 100644 Doxyfile delete mode 100755 PyUnitTests.py delete mode 100644 amount.cc delete mode 100644 amount.h delete mode 100644 balance.cc delete mode 100644 balance.h delete mode 100644 binary.cc delete mode 100644 binary.h delete mode 100755 compile delete mode 100644 context.h create mode 100644 contrib/ledger.vim create mode 100644 contrib/ledger.xcodeproj/johnw.mode1 create mode 100644 contrib/ledger.xcodeproj/johnw.pbxuser create mode 100644 contrib/ledger.xcodeproj/project.pbxproj delete mode 100644 csv.cc delete mode 100644 csv.h delete mode 100644 derive.cc delete mode 100644 derive.h create mode 100644 docs/Doxyfile create mode 100644 docs/ledger.info create mode 100644 docs/ledger.texi delete mode 100644 emacs.cc delete mode 100644 emacs.h delete mode 100644 error.h delete mode 100644 fdstream.hpp delete mode 100644 format.cc delete mode 100644 format.h delete mode 100644 gdtoa/gd_qnan.h delete mode 100644 gnucash.cc delete mode 100644 gnucash.h delete mode 100644 journal.cc delete mode 100644 journal.h delete mode 100644 ledger.el delete mode 100644 ledger.h delete mode 100644 ledger.info delete mode 100644 ledger.pdf delete mode 100644 ledger.texi delete mode 100644 ledger.vim delete mode 100644 ledger.xcodeproj/johnw.mode1 delete mode 100644 ledger.xcodeproj/johnw.pbxuser delete mode 100644 ledger.xcodeproj/project.pbxproj create mode 100644 lisp/ledger.el create mode 100644 lisp/timeclock.el delete mode 100644 main.cc delete mode 100644 mask.cc delete mode 100644 mask.h delete mode 100644 ofx.cc delete mode 100644 ofx.h delete mode 100644 option.cc delete mode 100644 option.h delete mode 100644 parser.h delete mode 100644 parsetime.yy delete mode 100644 py_amount.cc delete mode 100644 py_balance.cc delete mode 100644 py_eval.cc delete mode 100644 py_eval.h delete mode 100644 py_format.cc delete mode 100644 py_journal.cc delete mode 100644 py_option.cc delete mode 100644 py_parser.cc delete mode 100644 py_report.cc delete mode 100644 py_session.cc delete mode 100644 py_transform.cc delete mode 100644 py_value.cc delete mode 100644 py_xpath.cc delete mode 100644 pyfstream.h delete mode 100644 pyledger.cc delete mode 100644 pyledger.h delete mode 100644 qif.cc delete mode 100644 qif.h delete mode 100644 quotes.cc delete mode 100644 quotes.h delete mode 100644 reconcile.cc delete mode 100644 reconcile.h delete mode 100644 register.cc delete mode 100644 register.h delete mode 100644 report.cc delete mode 100644 report.h delete mode 100644 scantime.ll delete mode 100644 session.cc delete mode 100644 session.h create mode 100644 src/.gitignore create mode 100644 src/COPYRIGHT create mode 100644 src/amount.cc create mode 100644 src/amount.h create mode 100644 src/balance.cc create mode 100644 src/balance.h create mode 100644 src/binary.cc create mode 100644 src/binary.h create mode 100644 src/context.h create mode 100644 src/csv.cc create mode 100644 src/csv.h create mode 100644 src/derive.cc create mode 100644 src/derive.h create mode 100644 src/emacs.cc create mode 100644 src/emacs.h create mode 100644 src/error.h create mode 100644 src/fdstream.hpp create mode 100644 src/format.cc create mode 100644 src/format.h create mode 100644 src/gd_qnan.h create mode 100644 src/gnucash.cc create mode 100644 src/gnucash.h create mode 100644 src/journal.cc create mode 100644 src/journal.h create mode 100644 src/ledger.h create mode 100644 src/main.cc create mode 100644 src/mask.cc create mode 100644 src/mask.h create mode 100644 src/ofx.cc create mode 100644 src/ofx.h create mode 100644 src/option.cc create mode 100644 src/option.h create mode 100644 src/parser.h create mode 100644 src/py_amount.cc create mode 100644 src/py_balance.cc create mode 100644 src/py_format.cc create mode 100644 src/py_journal.cc create mode 100644 src/py_option.cc create mode 100644 src/py_parser.cc create mode 100644 src/py_report.cc create mode 100644 src/py_session.cc create mode 100644 src/py_transform.cc create mode 100644 src/py_value.cc create mode 100644 src/py_xpath.cc create mode 100644 src/pyfstream.h create mode 100644 src/pyinterp.cc create mode 100644 src/pyinterp.h create mode 100644 src/pyledger.cc create mode 100644 src/pyledger.h create mode 100644 src/qif.cc create mode 100644 src/qif.h create mode 100644 src/quotes.cc create mode 100644 src/quotes.h create mode 100644 src/reconcile.cc create mode 100644 src/reconcile.h create mode 100644 src/register.cc create mode 100644 src/register.h create mode 100644 src/report.cc create mode 100644 src/report.h create mode 100644 src/session.cc create mode 100644 src/session.h create mode 100644 src/system.hh create mode 100644 src/textual.cc create mode 100644 src/textual.h create mode 100644 src/times.cc create mode 100644 src/times.h create mode 100644 src/transform.cc create mode 100644 src/transform.h create mode 100644 src/utils.cc create mode 100644 src/utils.h create mode 100644 src/value.cc create mode 100644 src/value.h create mode 100644 src/xml.cc create mode 100644 src/xml.h create mode 100644 src/xmlparse.cc create mode 100644 src/xpath.cc create mode 100644 src/xpath.h delete mode 100644 system.hh delete mode 100644 tests/corelib/numerics/BasicAmount.cc delete mode 100644 tests/corelib/numerics/BasicAmount.h delete mode 100644 tests/corelib/numerics/Commodity.cc delete mode 100644 tests/corelib/numerics/Commodity.h delete mode 100644 tests/corelib/numerics/CommodityAmount.cc delete mode 100644 tests/corelib/numerics/CommodityAmount.h delete mode 100644 tests/corelib/numerics/DateTime.cc delete mode 100644 tests/corelib/numerics/DateTimeTest.h create mode 100644 tests/numerics/BasicAmount.cc create mode 100644 tests/numerics/BasicAmount.h create mode 100644 tests/numerics/Commodity.cc create mode 100644 tests/numerics/Commodity.h create mode 100644 tests/numerics/CommodityAmount.cc create mode 100644 tests/numerics/CommodityAmount.h create mode 100644 tests/numerics/DateTime.cc create mode 100644 tests/numerics/DateTimeTest.h create mode 100755 tests/python/PyUnitTests.py delete mode 100644 tests/python/corelib/.gitignore delete mode 100644 tests/python/corelib/__init__.py delete mode 100644 tests/python/corelib/balances/__init__.py delete mode 100644 tests/python/corelib/numerics/.gitignore delete mode 100644 tests/python/corelib/numerics/BasicAmount.py delete mode 100644 tests/python/corelib/numerics/CommodityAmount.py delete mode 100644 tests/python/corelib/numerics/__init__.py delete mode 100644 tests/python/corelib/values/__init__.py create mode 100644 tests/python/numerics/.gitignore create mode 100644 tests/python/numerics/BasicAmount.py create mode 100644 tests/python/numerics/CommodityAmount.py create mode 100644 tests/python/numerics/__init__.py create mode 100644 tests/python/numerics/balances/__init__.py create mode 100644 tests/python/numerics/values/__init__.py delete mode 100644 textual.cc delete mode 100644 textual.h delete mode 100644 timeclock.el delete mode 100644 times.cc delete mode 100644 times.h delete mode 100644 transform.cc delete mode 100644 transform.h delete mode 100644 utils.cc delete mode 100644 utils.h delete mode 100644 value.cc delete mode 100644 value.h delete mode 100644 xml.cc delete mode 100644 xml.h delete mode 100644 xmlparse.cc delete mode 100644 xmlparse.h delete mode 100644 xpath.cc delete mode 100644 xpath.h delete mode 100755 ylwrap diff --git a/COPYRIGHT b/COPYRIGHT deleted file mode 100644 index c9d4bd18..00000000 --- a/COPYRIGHT +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2003-2007, 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. - */ diff --git a/Doxyfile b/Doxyfile deleted file mode 100644 index a03b54d4..00000000 --- a/Doxyfile +++ /dev/null @@ -1,275 +0,0 @@ -# Doxyfile 1.5.1 - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -PROJECT_NAME = Ledger -PROJECT_NUMBER = 3.0 -OUTPUT_DIRECTORY = %builddir%/docs -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -USE_WINDOWS_ENCODING = NO -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = /Applications/Copied/ -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 8 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -BUILTIN_STL_SUPPORT = YES -DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = YES -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = NO -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -HIDE_UNDOC_MEMBERS = YES -HIDE_UNDOC_CLASSES = YES -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = NO -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = YES -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_DIRECTORIES = NO -FILE_VERSION_FILTER = -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = %srcdir% -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 -RECURSIVE = NO -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = * -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = YES -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES -REFERENCES_LINK_SOURCE = YES -USE_HTAGS = NO -VERBATIM_HEADERS = YES -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES -GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = YES -TREEVIEW_WIDTH = 250 -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = YES -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = usletter -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = NO -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = YES -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = YES -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = /Applications/Copied/Doxygen.app/Contents/Resources/dot -DOTFILE_DIRS = -MAX_DOT_GRAPH_WIDTH = 1024 -MAX_DOT_GRAPH_HEIGHT = 1024 -MAX_DOT_GRAPH_DEPTH = 1000 -DOT_TRANSPARENT = NO -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- -SEARCHENGINE = NO diff --git a/Makefile.am b/Makefile.am index c5ab1279..6a6e42a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,54 +27,52 @@ AM_LFLAGS = -o $(LEX_OUTPUT_ROOT).c #WARNFLAGS += -Wconversion -Wshorten-64-to-32 -Wsign-compare #WARNFLAGS += -Wmissing-field-initializers -pedantic-errors -libledger_la_CPPFLAGS = -I$(top_builddir)/gdtoa +libledger_la_CPPFLAGS = -I$(top_builddir)/gdtoa -I$(srcdir) -I$(srcdir)/src libledger_la_LDFLAGS = -release 3.0 libledger_la_SOURCES = \ - utils.cc \ - times.cc \ - amount.cc \ - quotes.cc \ - balance.cc \ - value.cc \ - xml.cc \ - xpath.cc \ - mask.cc \ - format.cc \ - \ - session.cc \ - journal.cc \ - textual.cc \ - binary.cc \ - xmlparse.cc \ - qif.cc \ - \ - report.cc \ - transform.cc \ - \ - register.cc \ - csv.cc \ - derive.cc \ - emacs.cc \ - reconcile.cc + src/session.cc \ + src/journal.cc \ + src/amount.cc \ + src/balance.cc \ + src/value.cc \ + src/binary.cc \ + src/qif.cc \ + src/textual.cc \ + src/quotes.cc \ + src/csv.cc \ + src/derive.cc \ + src/emacs.cc \ + src/format.cc \ + src/reconcile.cc \ + src/register.cc \ + src/report.cc \ + src/transform.cc \ + src/mask.cc \ + src/times.cc \ + src/utils.cc \ + src/xml.cc \ + src/xmlparse.cc \ + src/xpath.cc if HAVE_EXPAT libledger_la_CPPFLAGS += -DHAVE_EXPAT=1 -libledger_la_SOURCES += gnucash.cc +libledger_la_SOURCES += src/gnucash.cc endif if HAVE_XMLPARSE libledger_la_CPPFLAGS += -DHAVE_XMLPARSE=1 -libledger_la_SOURCES += gnucash.cc +libledger_la_SOURCES += src/gnucash.cc endif if HAVE_LIBOFX libledger_la_CPPFLAGS += -DHAVE_LIBOFX=1 -libledger_la_SOURCES += ofx.cc +libledger_la_SOURCES += src/ofx.cc endif if DEBUG libledger_la_CPPFLAGS += -DFULL_DEBUG endif if HAVE_BOOST_PYTHON libledger_la_CPPFLAGS += -DUSE_BOOST_PYTHON=1 +libledger_la_SOURCES += src/pyinterp.cc endif if USE_PCH @@ -82,11 +80,11 @@ libledger_la_CXXFLAGS = $(WARNFLAGS) nodist_libledger_la_SOURCES = system.hh.gch BUILT_SOURCES += system.hh.gch -CLEANFILES += system.hh.gch system.hh +CLEANFILES += system.hh.gch -$(top_builddir)/system.hh.gch: $(srcdir)/system.hh acconf.h $(srcdir)/fdstream.hpp +$(top_builddir)/system.hh.gch: $(srcdir)/src/system.hh acconf.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(libledger_la_CPPFLAGS) \ - -o $@ $(srcdir)/system.hh + -o $@ $(srcdir)/src/system.hh endif @@ -94,46 +92,44 @@ libpyledger_la_CPPFLAGS = $(libledger_la_CPPFLAGS) libpyledger_la_LDFLAGS = -release 3.0 libpyledger_la_SOURCES = \ - py_eval.cc \ - py_amount.cc + src/py_amount.cc pkginclude_HEADERS = \ - amount.h \ - times.h \ - balance.h \ - binary.h \ - context.h \ - csv.h \ - derive.h \ - emacs.h \ - error.h \ - fdstream.hpp \ - format.h \ - gnucash.h \ - journal.h \ - ledger.h \ - mask.h \ - ofx.h \ - option.h \ - parser.h \ - py_eval.h \ - pyfstream.h \ - pyledger.h \ - qif.h \ - quotes.h \ - reconcile.h \ - register.h \ - report.h \ - session.h \ - system.hh \ - textual.h \ - transform.h \ - utils.h \ - value.h \ - xml.h \ - xmlparse.h \ - xpath.h + src/amount.h \ + src/balance.h \ + src/binary.h \ + src/context.h \ + src/csv.h \ + src/derive.h \ + src/emacs.h \ + src/error.h \ + src/fdstream.hpp \ + src/format.h \ + src/gnucash.h \ + src/journal.h \ + src/ledger.h \ + src/mask.h \ + src/ofx.h \ + src/option.h \ + src/parser.h \ + src/pyinterp.h \ + src/pyfstream.h \ + src/pyledger.h \ + src/qif.h \ + src/quotes.h \ + src/reconcile.h \ + src/register.h \ + src/report.h \ + src/session.h \ + src/system.hh \ + src/textual.h \ + src/times.h \ + src/transform.h \ + src/utils.h \ + src/value.h \ + src/xml.h \ + src/xpath.h ###################################################################### @@ -141,19 +137,22 @@ bin_PROGRAMS = ledger ledger_CPPFLAGS = $(libledger_la_CPPFLAGS) ledger_CXXFLAGS = $(WARNFLAGS) -ledger_SOURCES = option.cc main.cc ledger_LDADD = $(LIBOBJS) libledger.la gdtoa/libgdtoa.la $(LEXLIB) ledger_LDFLAGS = -static # for the sake of command-line speed +ledger_SOURCES = \ + src/option.cc \ + src/main.cc + if HAVE_BOOST_PYTHON ledger_LDADD += libpyledger.la endif -info_TEXINFOS = ledger.texi +info_TEXINFOS = docs/ledger.texi ###################################################################### -dist_lisp_LISP = ledger.el timeclock.el +dist_lisp_LISP = lisp/ledger.el lisp/timeclock.el ###################################################################### @@ -166,7 +165,7 @@ CLEANFILES += ledger.so clean-local: rm -fr build -ledger_so_SOURCES = pyledger.cc +ledger_so_SOURCES = src/pyledger.cc ledger_so_DEPENDENCIES = libledger.la gdtoa/libgdtoa.la libpyledger.la PYLIBS = pyledger ledger gdtoa boost_date_time boost_regex boost_python gmp @@ -181,7 +180,8 @@ if HAVE_LIBOFX PYLIBS += ofx endif -ledger.so: pyledger.cc libledger.la gdtoa/libgdtoa.la libpyledger.la +ledger.so: src/pyledger.cc \ + libledger.la gdtoa/libgdtoa.la libpyledger.la SRCDIR="$(srcdir)" \ CFLAGS="$(CPPFLAGS) -I$(srcdir) $(libledger_la_CPPFLAGS)" \ LDFLAGS="$(LDFLAGS) -L. -L.libs -Lgdtoa -Lgdtoa/.libs" \ @@ -208,30 +208,32 @@ check_PROGRAMS = $(TESTS) nodist_UnitTests_SOURCES = tests/UnitTests.cc \ \ - tests/corelib/numerics/BasicAmount.cc \ - tests/corelib/numerics/CommodityAmount.cc \ - tests/corelib/numerics/DateTime.cc \ - tests/corelib/numerics/Commodity.cc + tests/numerics/BasicAmount.cc \ + tests/numerics/CommodityAmount.cc \ + tests/numerics/DateTime.cc \ + tests/numerics/Commodity.cc UnitTests_CPPFLAGS = -I$(srcdir)/tests $(libledger_la_CPPFLAGS) UnitTests_LDFLAGS = $(LIBADD_DL) UnitTests_LDADD = $(lib_LTLIBRARIES) gdtoa/libgdtoa.la -lcppunit -PyUnitTests_SOURCES = PyUnitTests.py +PyUnitTests_SOURCES = tests/python/PyUnitTests.py -PyUnitTests: PyUnitTests.py - cat $(srcdir)/PyUnitTests.py | sed "s/%srcdir%/$(ESC_srcdir)/g" \ - | sed "s/%builddir%/$(ESC_builddir)/g" > PyUnitTests - chmod 755 PyUnitTests +PyUnitTests: $(srcdir)/tests/python/PyUnitTests.py + cat $(srcdir)/tests/python/PyUnitTests.py \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ + | sed "s/%builddir%/$(ESC_builddir)/g" > $@ + chmod 755 $@ ###################################################################### DISTCLEANFILES = Doxyfile.gen -alldocs: ledger.info ledger.pdf doxygen-docs +alldocs: docs/ledger.info docs/ledger.pdf doxygen-docs -$(top_builddir)/Doxyfile.gen: $(srcdir)/Doxyfile - cat $(srcdir)/Doxyfile | sed "s/%srcdir%/$(ESC_srcdir)/g" \ +$(top_builddir)/Doxyfile.gen: $(srcdir)/docs/Doxyfile + cat $(srcdir)/docs/Doxyfile \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ | sed "s/%builddir%/$(ESC_builddir)/g" > $@ doxygen-docs: $(top_builddir)/Doxyfile.gen @@ -253,4 +255,4 @@ all-clean: maintainer-clean autom4te config.guess config.sub configure depcomp install-sh \ libtool ltconfig ltmain.sh missing stamp texinfo.tex \ Makefile.in mkinstalldirs elisp-comp elc-stamp elc-temp \ - py-compile + py-compile ylwrap compile diff --git a/Makefile.in b/Makefile.in index c8e2b999..0e74ca90 100644 --- a/Makefile.in +++ b/Makefile.in @@ -37,32 +37,33 @@ build_triplet = @build@ host_triplet = @host@ @HAVE_BOOST_PYTHON_TRUE@am__append_1 = libpyledger.la @HAVE_EXPAT_TRUE@am__append_2 = -DHAVE_EXPAT=1 -@HAVE_EXPAT_TRUE@am__append_3 = gnucash.cc +@HAVE_EXPAT_TRUE@am__append_3 = src/gnucash.cc @HAVE_XMLPARSE_TRUE@am__append_4 = -DHAVE_XMLPARSE=1 -@HAVE_XMLPARSE_TRUE@am__append_5 = gnucash.cc +@HAVE_XMLPARSE_TRUE@am__append_5 = src/gnucash.cc @HAVE_LIBOFX_TRUE@am__append_6 = -DHAVE_LIBOFX=1 -@HAVE_LIBOFX_TRUE@am__append_7 = ofx.cc +@HAVE_LIBOFX_TRUE@am__append_7 = src/ofx.cc @DEBUG_TRUE@am__append_8 = -DFULL_DEBUG @HAVE_BOOST_PYTHON_TRUE@am__append_9 = -DUSE_BOOST_PYTHON=1 -@USE_PCH_TRUE@am__append_10 = system.hh.gch -@USE_PCH_TRUE@am__append_11 = system.hh.gch system.hh +@HAVE_BOOST_PYTHON_TRUE@am__append_10 = src/pyinterp.cc +@USE_PCH_TRUE@am__append_11 = system.hh.gch +@USE_PCH_TRUE@am__append_12 = system.hh.gch bin_PROGRAMS = ledger$(EXEEXT) -@HAVE_BOOST_PYTHON_TRUE@am__append_12 = libpyledger.la +@HAVE_BOOST_PYTHON_TRUE@am__append_13 = libpyledger.la @HAVE_BOOST_PYTHON_TRUE@noinst_PROGRAMS = ledger.so$(EXEEXT) -@HAVE_BOOST_PYTHON_TRUE@am__append_13 = ledger.so -@HAVE_BOOST_PYTHON_TRUE@@HAVE_EXPAT_TRUE@am__append_14 = expat -@HAVE_BOOST_PYTHON_TRUE@@HAVE_XMLPARSE_TRUE@am__append_15 = xmlparse xmltok -@HAVE_BOOST_PYTHON_TRUE@@HAVE_LIBOFX_TRUE@am__append_16 = ofx +@HAVE_BOOST_PYTHON_TRUE@am__append_14 = ledger.so +@HAVE_BOOST_PYTHON_TRUE@@HAVE_EXPAT_TRUE@am__append_15 = expat +@HAVE_BOOST_PYTHON_TRUE@@HAVE_XMLPARSE_TRUE@am__append_16 = xmlparse xmltok +@HAVE_BOOST_PYTHON_TRUE@@HAVE_LIBOFX_TRUE@am__append_17 = ofx TESTS = UnitTests$(EXEEXT) $(am__EXEEXT_1) -@HAVE_BOOST_PYTHON_TRUE@am__append_17 = PyUnitTests +@HAVE_BOOST_PYTHON_TRUE@am__append_18 = PyUnitTests check_PROGRAMS = $(am__EXEEXT_2) subdir = . DIST_COMMON = README $(am__configure_deps) $(dist_lisp_LISP) \ $(pkginclude_HEADERS) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in $(srcdir)/acconf.h.in \ $(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \ - TODO compile config.guess config.sub depcomp elisp-comp \ - install-sh ltmain.sh missing texinfo.tex ylwrap + TODO config.guess config.sub depcomp elisp-comp install-sh \ + ltmain.sh missing texinfo.tex ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/configure.in am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ @@ -84,26 +85,31 @@ am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \ libLTLIBRARIES_INSTALL = $(INSTALL) LTLIBRARIES = $(lib_LTLIBRARIES) libledger_la_LIBADD = -am__libledger_la_SOURCES_DIST = utils.cc times.cc amount.cc quotes.cc \ - balance.cc value.cc xml.cc xpath.cc mask.cc format.cc \ - session.cc journal.cc textual.cc binary.cc xmlparse.cc qif.cc \ - report.cc transform.cc register.cc csv.cc derive.cc emacs.cc \ - reconcile.cc gnucash.cc ofx.cc +am__libledger_la_SOURCES_DIST = src/session.cc src/journal.cc \ + src/amount.cc src/balance.cc src/value.cc src/binary.cc \ + src/qif.cc src/textual.cc src/quotes.cc src/csv.cc \ + src/derive.cc src/emacs.cc src/format.cc src/reconcile.cc \ + src/register.cc src/report.cc src/transform.cc src/mask.cc \ + src/times.cc src/utils.cc src/xml.cc src/xmlparse.cc \ + src/xpath.cc src/gnucash.cc src/ofx.cc src/pyinterp.cc @HAVE_EXPAT_TRUE@am__objects_1 = libledger_la-gnucash.lo @HAVE_XMLPARSE_TRUE@am__objects_2 = libledger_la-gnucash.lo @HAVE_LIBOFX_TRUE@am__objects_3 = libledger_la-ofx.lo -am_libledger_la_OBJECTS = libledger_la-utils.lo libledger_la-times.lo \ - libledger_la-amount.lo libledger_la-quotes.lo \ +@HAVE_BOOST_PYTHON_TRUE@am__objects_4 = libledger_la-pyinterp.lo +am_libledger_la_OBJECTS = libledger_la-session.lo \ + libledger_la-journal.lo libledger_la-amount.lo \ libledger_la-balance.lo libledger_la-value.lo \ - libledger_la-xml.lo libledger_la-xpath.lo libledger_la-mask.lo \ - libledger_la-format.lo libledger_la-session.lo \ - libledger_la-journal.lo libledger_la-textual.lo \ - libledger_la-binary.lo libledger_la-xmlparse.lo \ - libledger_la-qif.lo libledger_la-report.lo \ - libledger_la-transform.lo libledger_la-register.lo \ + libledger_la-binary.lo libledger_la-qif.lo \ + libledger_la-textual.lo libledger_la-quotes.lo \ libledger_la-csv.lo libledger_la-derive.lo \ - libledger_la-emacs.lo libledger_la-reconcile.lo \ - $(am__objects_1) $(am__objects_2) $(am__objects_3) + libledger_la-emacs.lo libledger_la-format.lo \ + libledger_la-reconcile.lo libledger_la-register.lo \ + libledger_la-report.lo libledger_la-transform.lo \ + libledger_la-mask.lo libledger_la-times.lo \ + libledger_la-utils.lo libledger_la-xml.lo \ + libledger_la-xmlparse.lo libledger_la-xpath.lo \ + $(am__objects_1) $(am__objects_2) $(am__objects_3) \ + $(am__objects_4) nodist_libledger_la_OBJECTS = libledger_la_OBJECTS = $(am_libledger_la_OBJECTS) \ $(nodist_libledger_la_OBJECTS) @@ -111,8 +117,7 @@ libledger_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libledger_la_CXXFLAGS) \ $(CXXFLAGS) $(libledger_la_LDFLAGS) $(LDFLAGS) -o $@ libpyledger_la_LIBADD = -am_libpyledger_la_OBJECTS = libpyledger_la-py_eval.lo \ - libpyledger_la-py_amount.lo +am_libpyledger_la_OBJECTS = libpyledger_la-py_amount.lo libpyledger_la_OBJECTS = $(am_libpyledger_la_OBJECTS) libpyledger_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ @@ -137,11 +142,11 @@ UnitTests_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ am_ledger_OBJECTS = ledger-option.$(OBJEXT) ledger-main.$(OBJEXT) ledger_OBJECTS = $(am_ledger_OBJECTS) ledger_DEPENDENCIES = $(LIBOBJS) libledger.la gdtoa/libgdtoa.la \ - $(am__append_12) + $(am__append_13) ledger_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CXXLD) $(ledger_CXXFLAGS) $(CXXFLAGS) \ $(ledger_LDFLAGS) $(LDFLAGS) -o $@ -am__ledger_so_SOURCES_DIST = pyledger.cc +am__ledger_so_SOURCES_DIST = src/pyledger.cc @HAVE_BOOST_PYTHON_TRUE@am_ledger_so_OBJECTS = pyledger.$(OBJEXT) ledger_so_OBJECTS = $(am_ledger_so_OBJECTS) ledger_so_LDADD = $(LDADD) @@ -173,13 +178,14 @@ SOURCES = $(libledger_la_SOURCES) $(nodist_libledger_la_SOURCES) \ DIST_SOURCES = $(am__libledger_la_SOURCES_DIST) \ $(libpyledger_la_SOURCES) $(PyUnitTests_SOURCES) \ $(ledger_SOURCES) $(am__ledger_so_SOURCES_DIST) -INFO_DEPS = $(srcdir)/ledger.info +am__dirstamp = $(am__leading_dot)dirstamp +INFO_DEPS = $(srcdir)/docs/ledger.info am__TEXINFO_TEX_DIR = $(srcdir) -DVIS = ledger.dvi -PDFS = ledger.pdf -PSS = ledger.ps -HTMLS = ledger.html -TEXINFOS = ledger.texi +DVIS = docs/ledger.dvi +PDFS = docs/ledger.pdf +PSS = docs/ledger.ps +HTMLS = docs/ledger.html +TEXINFOS = docs/ledger.texi TEXI2DVI = texi2dvi TEXI2PDF = $(TEXI2DVI) --pdf --batch MAKEINFOHTML = $(MAKEINFO) --html @@ -194,7 +200,7 @@ RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ ps-recursive uninstall-recursive dist_lispLISP_INSTALL = $(INSTALL_DATA) LISP = $(dist_lisp_LISP) -am__ELFILES = ledger.el timeclock.el +am__ELFILES = lisp/ledger.el lisp/timeclock.el am__ELCFILES = $(am__ELFILES:.el=.elc) ELCFILES = $(LISP:.el=.elc) elisp_comp = $(top_srcdir)/elisp-comp @@ -335,8 +341,8 @@ target_alias = @target_alias@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ SUBDIRS = gdtoa -BUILT_SOURCES = $(am__append_10) -CLEANFILES = $(am__append_11) $(am__append_13) +BUILT_SOURCES = $(am__append_11) +CLEANFILES = $(am__append_12) $(am__append_14) ESC_srcdir = `echo "$(srcdir)" | sed 's/\//\\\\\//g'` ESC_builddir = `echo "$(top_builddir)" | sed 's/\//\\\\\//g'` ESC_distdir = `echo "$(distdir)" | sed 's/\//\\\\\//g'` @@ -349,87 +355,91 @@ AM_LFLAGS = -o $(LEX_OUTPUT_ROOT).c #WARNFLAGS += -Wcast-qual -Wcast-align -Wwrite-strings -Wconversion #WARNFLAGS += -Wconversion -Wshorten-64-to-32 -Wsign-compare #WARNFLAGS += -Wmissing-field-initializers -pedantic-errors -libledger_la_CPPFLAGS = -I$(top_builddir)/gdtoa $(am__append_2) \ - $(am__append_4) $(am__append_6) $(am__append_8) \ - $(am__append_9) +libledger_la_CPPFLAGS = -I$(top_builddir)/gdtoa -I$(srcdir) \ + -I$(srcdir)/src $(am__append_2) $(am__append_4) \ + $(am__append_6) $(am__append_8) $(am__append_9) libledger_la_LDFLAGS = -release 3.0 -libledger_la_SOURCES = utils.cc times.cc amount.cc quotes.cc \ - balance.cc value.cc xml.cc xpath.cc mask.cc format.cc \ - session.cc journal.cc textual.cc binary.cc xmlparse.cc qif.cc \ - report.cc transform.cc register.cc csv.cc derive.cc emacs.cc \ - reconcile.cc $(am__append_3) $(am__append_5) $(am__append_7) +libledger_la_SOURCES = src/session.cc src/journal.cc src/amount.cc \ + src/balance.cc src/value.cc src/binary.cc src/qif.cc \ + src/textual.cc src/quotes.cc src/csv.cc src/derive.cc \ + src/emacs.cc src/format.cc src/reconcile.cc src/register.cc \ + src/report.cc src/transform.cc src/mask.cc src/times.cc \ + src/utils.cc src/xml.cc src/xmlparse.cc src/xpath.cc \ + $(am__append_3) $(am__append_5) $(am__append_7) \ + $(am__append_10) @USE_PCH_TRUE@libledger_la_CXXFLAGS = $(WARNFLAGS) @USE_PCH_TRUE@nodist_libledger_la_SOURCES = system.hh.gch libpyledger_la_CPPFLAGS = $(libledger_la_CPPFLAGS) libpyledger_la_LDFLAGS = -release 3.0 libpyledger_la_SOURCES = \ - py_eval.cc \ - py_amount.cc + src/py_amount.cc pkginclude_HEADERS = \ - amount.h \ - times.h \ - balance.h \ - binary.h \ - context.h \ - csv.h \ - derive.h \ - emacs.h \ - error.h \ - fdstream.hpp \ - format.h \ - gnucash.h \ - journal.h \ - ledger.h \ - mask.h \ - ofx.h \ - option.h \ - parser.h \ - py_eval.h \ - pyfstream.h \ - pyledger.h \ - qif.h \ - quotes.h \ - reconcile.h \ - register.h \ - report.h \ - session.h \ - system.hh \ - textual.h \ - transform.h \ - utils.h \ - value.h \ - xml.h \ - xmlparse.h \ - xpath.h + src/amount.h \ + src/balance.h \ + src/binary.h \ + src/context.h \ + src/csv.h \ + src/derive.h \ + src/emacs.h \ + src/error.h \ + src/fdstream.hpp \ + src/format.h \ + src/gnucash.h \ + src/journal.h \ + src/ledger.h \ + src/mask.h \ + src/ofx.h \ + src/option.h \ + src/parser.h \ + src/pyinterp.h \ + src/pyfstream.h \ + src/pyledger.h \ + src/qif.h \ + src/quotes.h \ + src/reconcile.h \ + src/register.h \ + src/report.h \ + src/session.h \ + src/system.hh \ + src/textual.h \ + src/times.h \ + src/transform.h \ + src/utils.h \ + src/value.h \ + src/xml.h \ + src/xpath.h ledger_CPPFLAGS = $(libledger_la_CPPFLAGS) ledger_CXXFLAGS = $(WARNFLAGS) -ledger_SOURCES = option.cc main.cc ledger_LDADD = $(LIBOBJS) libledger.la gdtoa/libgdtoa.la $(LEXLIB) \ - $(am__append_12) + $(am__append_13) ledger_LDFLAGS = -static # for the sake of command-line speed -info_TEXINFOS = ledger.texi +ledger_SOURCES = \ + src/option.cc \ + src/main.cc + +info_TEXINFOS = docs/ledger.texi ###################################################################### -dist_lisp_LISP = ledger.el timeclock.el -@HAVE_BOOST_PYTHON_TRUE@ledger_so_SOURCES = pyledger.cc +dist_lisp_LISP = lisp/ledger.el lisp/timeclock.el +@HAVE_BOOST_PYTHON_TRUE@ledger_so_SOURCES = src/pyledger.cc @HAVE_BOOST_PYTHON_TRUE@ledger_so_DEPENDENCIES = libledger.la gdtoa/libgdtoa.la libpyledger.la @HAVE_BOOST_PYTHON_TRUE@PYLIBS = pyledger ledger gdtoa boost_date_time \ @HAVE_BOOST_PYTHON_TRUE@ boost_regex boost_python gmp \ -@HAVE_BOOST_PYTHON_TRUE@ $(am__append_14) $(am__append_15) \ -@HAVE_BOOST_PYTHON_TRUE@ $(am__append_16) +@HAVE_BOOST_PYTHON_TRUE@ $(am__append_15) $(am__append_16) \ +@HAVE_BOOST_PYTHON_TRUE@ $(am__append_17) nodist_UnitTests_SOURCES = tests/UnitTests.cc \ \ - tests/corelib/numerics/BasicAmount.cc \ - tests/corelib/numerics/CommodityAmount.cc \ - tests/corelib/numerics/DateTime.cc \ - tests/corelib/numerics/Commodity.cc + tests/numerics/BasicAmount.cc \ + tests/numerics/CommodityAmount.cc \ + tests/numerics/DateTime.cc \ + tests/numerics/Commodity.cc UnitTests_CPPFLAGS = -I$(srcdir)/tests $(libledger_la_CPPFLAGS) UnitTests_LDFLAGS = $(LIBADD_DL) UnitTests_LDADD = $(lib_LTLIBRARIES) gdtoa/libgdtoa.la -lcppunit -PyUnitTests_SOURCES = PyUnitTests.py +PyUnitTests_SOURCES = tests/python/PyUnitTests.py ###################################################################### DISTCLEANFILES = Doxyfile.gen @@ -437,7 +447,7 @@ all: $(BUILT_SOURCES) acconf.h $(MAKE) $(AM_MAKEFLAGS) all-recursive .SUFFIXES: -.SUFFIXES: .cc .dvi .html .info .lo .o .obj .pdf .ps .texi +.SUFFIXES: .cc .dvi .lo .o .obj .ps am--refresh: @: $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) @@ -595,6 +605,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-journal.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-mask.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-ofx.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-pyinterp.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-qif.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-quotes.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-reconcile.Plo@am__quote@ @@ -610,7 +621,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-xmlparse.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libledger_la-xpath.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpyledger_la-py_amount.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpyledger_la-py_eval.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pyledger.Po@am__quote@ .cc.o: @@ -634,194 +644,194 @@ distclean-compile: @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -c -o $@ $< -libledger_la-utils.lo: utils.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-utils.lo -MD -MP -MF $(DEPDIR)/libledger_la-utils.Tpo -c -o libledger_la-utils.lo `test -f 'utils.cc' || echo '$(srcdir)/'`utils.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-utils.Tpo $(DEPDIR)/libledger_la-utils.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='utils.cc' object='libledger_la-utils.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-session.lo: src/session.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-session.lo -MD -MP -MF $(DEPDIR)/libledger_la-session.Tpo -c -o libledger_la-session.lo `test -f 'src/session.cc' || echo '$(srcdir)/'`src/session.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-session.Tpo $(DEPDIR)/libledger_la-session.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/session.cc' object='libledger_la-session.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-utils.lo `test -f 'utils.cc' || echo '$(srcdir)/'`utils.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-session.lo `test -f 'src/session.cc' || echo '$(srcdir)/'`src/session.cc -libledger_la-times.lo: times.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-times.lo -MD -MP -MF $(DEPDIR)/libledger_la-times.Tpo -c -o libledger_la-times.lo `test -f 'times.cc' || echo '$(srcdir)/'`times.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-times.Tpo $(DEPDIR)/libledger_la-times.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='times.cc' object='libledger_la-times.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-journal.lo: src/journal.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-journal.lo -MD -MP -MF $(DEPDIR)/libledger_la-journal.Tpo -c -o libledger_la-journal.lo `test -f 'src/journal.cc' || echo '$(srcdir)/'`src/journal.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-journal.Tpo $(DEPDIR)/libledger_la-journal.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/journal.cc' object='libledger_la-journal.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-times.lo `test -f 'times.cc' || echo '$(srcdir)/'`times.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-journal.lo `test -f 'src/journal.cc' || echo '$(srcdir)/'`src/journal.cc -libledger_la-amount.lo: amount.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-amount.lo -MD -MP -MF $(DEPDIR)/libledger_la-amount.Tpo -c -o libledger_la-amount.lo `test -f 'amount.cc' || echo '$(srcdir)/'`amount.cc +libledger_la-amount.lo: src/amount.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-amount.lo -MD -MP -MF $(DEPDIR)/libledger_la-amount.Tpo -c -o libledger_la-amount.lo `test -f 'src/amount.cc' || echo '$(srcdir)/'`src/amount.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-amount.Tpo $(DEPDIR)/libledger_la-amount.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='amount.cc' object='libledger_la-amount.lo' libtool=yes @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-amount.lo `test -f 'amount.cc' || echo '$(srcdir)/'`amount.cc - -libledger_la-quotes.lo: quotes.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-quotes.lo -MD -MP -MF $(DEPDIR)/libledger_la-quotes.Tpo -c -o libledger_la-quotes.lo `test -f 'quotes.cc' || echo '$(srcdir)/'`quotes.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-quotes.Tpo $(DEPDIR)/libledger_la-quotes.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='quotes.cc' object='libledger_la-quotes.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/amount.cc' object='libledger_la-amount.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-quotes.lo `test -f 'quotes.cc' || echo '$(srcdir)/'`quotes.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-amount.lo `test -f 'src/amount.cc' || echo '$(srcdir)/'`src/amount.cc -libledger_la-balance.lo: balance.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-balance.lo -MD -MP -MF $(DEPDIR)/libledger_la-balance.Tpo -c -o libledger_la-balance.lo `test -f 'balance.cc' || echo '$(srcdir)/'`balance.cc +libledger_la-balance.lo: src/balance.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-balance.lo -MD -MP -MF $(DEPDIR)/libledger_la-balance.Tpo -c -o libledger_la-balance.lo `test -f 'src/balance.cc' || echo '$(srcdir)/'`src/balance.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-balance.Tpo $(DEPDIR)/libledger_la-balance.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='balance.cc' object='libledger_la-balance.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/balance.cc' object='libledger_la-balance.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-balance.lo `test -f 'balance.cc' || echo '$(srcdir)/'`balance.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-balance.lo `test -f 'src/balance.cc' || echo '$(srcdir)/'`src/balance.cc -libledger_la-value.lo: value.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-value.lo -MD -MP -MF $(DEPDIR)/libledger_la-value.Tpo -c -o libledger_la-value.lo `test -f 'value.cc' || echo '$(srcdir)/'`value.cc +libledger_la-value.lo: src/value.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-value.lo -MD -MP -MF $(DEPDIR)/libledger_la-value.Tpo -c -o libledger_la-value.lo `test -f 'src/value.cc' || echo '$(srcdir)/'`src/value.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-value.Tpo $(DEPDIR)/libledger_la-value.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='value.cc' object='libledger_la-value.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/value.cc' object='libledger_la-value.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-value.lo `test -f 'value.cc' || echo '$(srcdir)/'`value.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-value.lo `test -f 'src/value.cc' || echo '$(srcdir)/'`src/value.cc -libledger_la-xml.lo: xml.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-xml.lo -MD -MP -MF $(DEPDIR)/libledger_la-xml.Tpo -c -o libledger_la-xml.lo `test -f 'xml.cc' || echo '$(srcdir)/'`xml.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-xml.Tpo $(DEPDIR)/libledger_la-xml.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='xml.cc' object='libledger_la-xml.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-binary.lo: src/binary.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-binary.lo -MD -MP -MF $(DEPDIR)/libledger_la-binary.Tpo -c -o libledger_la-binary.lo `test -f 'src/binary.cc' || echo '$(srcdir)/'`src/binary.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-binary.Tpo $(DEPDIR)/libledger_la-binary.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/binary.cc' object='libledger_la-binary.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-xml.lo `test -f 'xml.cc' || echo '$(srcdir)/'`xml.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-binary.lo `test -f 'src/binary.cc' || echo '$(srcdir)/'`src/binary.cc -libledger_la-xpath.lo: xpath.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-xpath.lo -MD -MP -MF $(DEPDIR)/libledger_la-xpath.Tpo -c -o libledger_la-xpath.lo `test -f 'xpath.cc' || echo '$(srcdir)/'`xpath.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-xpath.Tpo $(DEPDIR)/libledger_la-xpath.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='xpath.cc' object='libledger_la-xpath.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-qif.lo: src/qif.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-qif.lo -MD -MP -MF $(DEPDIR)/libledger_la-qif.Tpo -c -o libledger_la-qif.lo `test -f 'src/qif.cc' || echo '$(srcdir)/'`src/qif.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-qif.Tpo $(DEPDIR)/libledger_la-qif.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/qif.cc' object='libledger_la-qif.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-xpath.lo `test -f 'xpath.cc' || echo '$(srcdir)/'`xpath.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-qif.lo `test -f 'src/qif.cc' || echo '$(srcdir)/'`src/qif.cc -libledger_la-mask.lo: mask.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-mask.lo -MD -MP -MF $(DEPDIR)/libledger_la-mask.Tpo -c -o libledger_la-mask.lo `test -f 'mask.cc' || echo '$(srcdir)/'`mask.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-mask.Tpo $(DEPDIR)/libledger_la-mask.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='mask.cc' object='libledger_la-mask.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-textual.lo: src/textual.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-textual.lo -MD -MP -MF $(DEPDIR)/libledger_la-textual.Tpo -c -o libledger_la-textual.lo `test -f 'src/textual.cc' || echo '$(srcdir)/'`src/textual.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-textual.Tpo $(DEPDIR)/libledger_la-textual.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/textual.cc' object='libledger_la-textual.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-mask.lo `test -f 'mask.cc' || echo '$(srcdir)/'`mask.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-textual.lo `test -f 'src/textual.cc' || echo '$(srcdir)/'`src/textual.cc -libledger_la-format.lo: format.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-format.lo -MD -MP -MF $(DEPDIR)/libledger_la-format.Tpo -c -o libledger_la-format.lo `test -f 'format.cc' || echo '$(srcdir)/'`format.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-format.Tpo $(DEPDIR)/libledger_la-format.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='format.cc' object='libledger_la-format.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-quotes.lo: src/quotes.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-quotes.lo -MD -MP -MF $(DEPDIR)/libledger_la-quotes.Tpo -c -o libledger_la-quotes.lo `test -f 'src/quotes.cc' || echo '$(srcdir)/'`src/quotes.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-quotes.Tpo $(DEPDIR)/libledger_la-quotes.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/quotes.cc' object='libledger_la-quotes.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-format.lo `test -f 'format.cc' || echo '$(srcdir)/'`format.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-quotes.lo `test -f 'src/quotes.cc' || echo '$(srcdir)/'`src/quotes.cc -libledger_la-session.lo: session.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-session.lo -MD -MP -MF $(DEPDIR)/libledger_la-session.Tpo -c -o libledger_la-session.lo `test -f 'session.cc' || echo '$(srcdir)/'`session.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-session.Tpo $(DEPDIR)/libledger_la-session.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='session.cc' object='libledger_la-session.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-csv.lo: src/csv.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-csv.lo -MD -MP -MF $(DEPDIR)/libledger_la-csv.Tpo -c -o libledger_la-csv.lo `test -f 'src/csv.cc' || echo '$(srcdir)/'`src/csv.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-csv.Tpo $(DEPDIR)/libledger_la-csv.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/csv.cc' object='libledger_la-csv.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-session.lo `test -f 'session.cc' || echo '$(srcdir)/'`session.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-csv.lo `test -f 'src/csv.cc' || echo '$(srcdir)/'`src/csv.cc -libledger_la-journal.lo: journal.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-journal.lo -MD -MP -MF $(DEPDIR)/libledger_la-journal.Tpo -c -o libledger_la-journal.lo `test -f 'journal.cc' || echo '$(srcdir)/'`journal.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-journal.Tpo $(DEPDIR)/libledger_la-journal.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='journal.cc' object='libledger_la-journal.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-derive.lo: src/derive.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-derive.lo -MD -MP -MF $(DEPDIR)/libledger_la-derive.Tpo -c -o libledger_la-derive.lo `test -f 'src/derive.cc' || echo '$(srcdir)/'`src/derive.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-derive.Tpo $(DEPDIR)/libledger_la-derive.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/derive.cc' object='libledger_la-derive.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-journal.lo `test -f 'journal.cc' || echo '$(srcdir)/'`journal.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-derive.lo `test -f 'src/derive.cc' || echo '$(srcdir)/'`src/derive.cc -libledger_la-textual.lo: textual.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-textual.lo -MD -MP -MF $(DEPDIR)/libledger_la-textual.Tpo -c -o libledger_la-textual.lo `test -f 'textual.cc' || echo '$(srcdir)/'`textual.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-textual.Tpo $(DEPDIR)/libledger_la-textual.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='textual.cc' object='libledger_la-textual.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-emacs.lo: src/emacs.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-emacs.lo -MD -MP -MF $(DEPDIR)/libledger_la-emacs.Tpo -c -o libledger_la-emacs.lo `test -f 'src/emacs.cc' || echo '$(srcdir)/'`src/emacs.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-emacs.Tpo $(DEPDIR)/libledger_la-emacs.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/emacs.cc' object='libledger_la-emacs.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-textual.lo `test -f 'textual.cc' || echo '$(srcdir)/'`textual.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-emacs.lo `test -f 'src/emacs.cc' || echo '$(srcdir)/'`src/emacs.cc -libledger_la-binary.lo: binary.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-binary.lo -MD -MP -MF $(DEPDIR)/libledger_la-binary.Tpo -c -o libledger_la-binary.lo `test -f 'binary.cc' || echo '$(srcdir)/'`binary.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-binary.Tpo $(DEPDIR)/libledger_la-binary.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='binary.cc' object='libledger_la-binary.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-format.lo: src/format.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-format.lo -MD -MP -MF $(DEPDIR)/libledger_la-format.Tpo -c -o libledger_la-format.lo `test -f 'src/format.cc' || echo '$(srcdir)/'`src/format.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-format.Tpo $(DEPDIR)/libledger_la-format.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/format.cc' object='libledger_la-format.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-binary.lo `test -f 'binary.cc' || echo '$(srcdir)/'`binary.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-format.lo `test -f 'src/format.cc' || echo '$(srcdir)/'`src/format.cc -libledger_la-xmlparse.lo: xmlparse.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-xmlparse.lo -MD -MP -MF $(DEPDIR)/libledger_la-xmlparse.Tpo -c -o libledger_la-xmlparse.lo `test -f 'xmlparse.cc' || echo '$(srcdir)/'`xmlparse.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-xmlparse.Tpo $(DEPDIR)/libledger_la-xmlparse.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='xmlparse.cc' object='libledger_la-xmlparse.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-reconcile.lo: src/reconcile.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-reconcile.lo -MD -MP -MF $(DEPDIR)/libledger_la-reconcile.Tpo -c -o libledger_la-reconcile.lo `test -f 'src/reconcile.cc' || echo '$(srcdir)/'`src/reconcile.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-reconcile.Tpo $(DEPDIR)/libledger_la-reconcile.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/reconcile.cc' object='libledger_la-reconcile.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-xmlparse.lo `test -f 'xmlparse.cc' || echo '$(srcdir)/'`xmlparse.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-reconcile.lo `test -f 'src/reconcile.cc' || echo '$(srcdir)/'`src/reconcile.cc -libledger_la-qif.lo: qif.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-qif.lo -MD -MP -MF $(DEPDIR)/libledger_la-qif.Tpo -c -o libledger_la-qif.lo `test -f 'qif.cc' || echo '$(srcdir)/'`qif.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-qif.Tpo $(DEPDIR)/libledger_la-qif.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='qif.cc' object='libledger_la-qif.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-register.lo: src/register.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-register.lo -MD -MP -MF $(DEPDIR)/libledger_la-register.Tpo -c -o libledger_la-register.lo `test -f 'src/register.cc' || echo '$(srcdir)/'`src/register.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-register.Tpo $(DEPDIR)/libledger_la-register.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/register.cc' object='libledger_la-register.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-qif.lo `test -f 'qif.cc' || echo '$(srcdir)/'`qif.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-register.lo `test -f 'src/register.cc' || echo '$(srcdir)/'`src/register.cc -libledger_la-report.lo: report.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-report.lo -MD -MP -MF $(DEPDIR)/libledger_la-report.Tpo -c -o libledger_la-report.lo `test -f 'report.cc' || echo '$(srcdir)/'`report.cc +libledger_la-report.lo: src/report.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-report.lo -MD -MP -MF $(DEPDIR)/libledger_la-report.Tpo -c -o libledger_la-report.lo `test -f 'src/report.cc' || echo '$(srcdir)/'`src/report.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-report.Tpo $(DEPDIR)/libledger_la-report.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='report.cc' object='libledger_la-report.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/report.cc' object='libledger_la-report.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-report.lo `test -f 'report.cc' || echo '$(srcdir)/'`report.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-report.lo `test -f 'src/report.cc' || echo '$(srcdir)/'`src/report.cc -libledger_la-transform.lo: transform.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-transform.lo -MD -MP -MF $(DEPDIR)/libledger_la-transform.Tpo -c -o libledger_la-transform.lo `test -f 'transform.cc' || echo '$(srcdir)/'`transform.cc +libledger_la-transform.lo: src/transform.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-transform.lo -MD -MP -MF $(DEPDIR)/libledger_la-transform.Tpo -c -o libledger_la-transform.lo `test -f 'src/transform.cc' || echo '$(srcdir)/'`src/transform.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-transform.Tpo $(DEPDIR)/libledger_la-transform.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='transform.cc' object='libledger_la-transform.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/transform.cc' object='libledger_la-transform.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-transform.lo `test -f 'transform.cc' || echo '$(srcdir)/'`transform.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-transform.lo `test -f 'src/transform.cc' || echo '$(srcdir)/'`src/transform.cc -libledger_la-register.lo: register.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-register.lo -MD -MP -MF $(DEPDIR)/libledger_la-register.Tpo -c -o libledger_la-register.lo `test -f 'register.cc' || echo '$(srcdir)/'`register.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-register.Tpo $(DEPDIR)/libledger_la-register.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='register.cc' object='libledger_la-register.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-mask.lo: src/mask.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-mask.lo -MD -MP -MF $(DEPDIR)/libledger_la-mask.Tpo -c -o libledger_la-mask.lo `test -f 'src/mask.cc' || echo '$(srcdir)/'`src/mask.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-mask.Tpo $(DEPDIR)/libledger_la-mask.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/mask.cc' object='libledger_la-mask.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-register.lo `test -f 'register.cc' || echo '$(srcdir)/'`register.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-mask.lo `test -f 'src/mask.cc' || echo '$(srcdir)/'`src/mask.cc -libledger_la-csv.lo: csv.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-csv.lo -MD -MP -MF $(DEPDIR)/libledger_la-csv.Tpo -c -o libledger_la-csv.lo `test -f 'csv.cc' || echo '$(srcdir)/'`csv.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-csv.Tpo $(DEPDIR)/libledger_la-csv.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='csv.cc' object='libledger_la-csv.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-times.lo: src/times.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-times.lo -MD -MP -MF $(DEPDIR)/libledger_la-times.Tpo -c -o libledger_la-times.lo `test -f 'src/times.cc' || echo '$(srcdir)/'`src/times.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-times.Tpo $(DEPDIR)/libledger_la-times.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/times.cc' object='libledger_la-times.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-csv.lo `test -f 'csv.cc' || echo '$(srcdir)/'`csv.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-times.lo `test -f 'src/times.cc' || echo '$(srcdir)/'`src/times.cc -libledger_la-derive.lo: derive.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-derive.lo -MD -MP -MF $(DEPDIR)/libledger_la-derive.Tpo -c -o libledger_la-derive.lo `test -f 'derive.cc' || echo '$(srcdir)/'`derive.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-derive.Tpo $(DEPDIR)/libledger_la-derive.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='derive.cc' object='libledger_la-derive.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-utils.lo: src/utils.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-utils.lo -MD -MP -MF $(DEPDIR)/libledger_la-utils.Tpo -c -o libledger_la-utils.lo `test -f 'src/utils.cc' || echo '$(srcdir)/'`src/utils.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-utils.Tpo $(DEPDIR)/libledger_la-utils.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/utils.cc' object='libledger_la-utils.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-derive.lo `test -f 'derive.cc' || echo '$(srcdir)/'`derive.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-utils.lo `test -f 'src/utils.cc' || echo '$(srcdir)/'`src/utils.cc -libledger_la-emacs.lo: emacs.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-emacs.lo -MD -MP -MF $(DEPDIR)/libledger_la-emacs.Tpo -c -o libledger_la-emacs.lo `test -f 'emacs.cc' || echo '$(srcdir)/'`emacs.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-emacs.Tpo $(DEPDIR)/libledger_la-emacs.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='emacs.cc' object='libledger_la-emacs.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-xml.lo: src/xml.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-xml.lo -MD -MP -MF $(DEPDIR)/libledger_la-xml.Tpo -c -o libledger_la-xml.lo `test -f 'src/xml.cc' || echo '$(srcdir)/'`src/xml.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-xml.Tpo $(DEPDIR)/libledger_la-xml.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/xml.cc' object='libledger_la-xml.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-emacs.lo `test -f 'emacs.cc' || echo '$(srcdir)/'`emacs.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-xml.lo `test -f 'src/xml.cc' || echo '$(srcdir)/'`src/xml.cc -libledger_la-reconcile.lo: reconcile.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-reconcile.lo -MD -MP -MF $(DEPDIR)/libledger_la-reconcile.Tpo -c -o libledger_la-reconcile.lo `test -f 'reconcile.cc' || echo '$(srcdir)/'`reconcile.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-reconcile.Tpo $(DEPDIR)/libledger_la-reconcile.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='reconcile.cc' object='libledger_la-reconcile.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-xmlparse.lo: src/xmlparse.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-xmlparse.lo -MD -MP -MF $(DEPDIR)/libledger_la-xmlparse.Tpo -c -o libledger_la-xmlparse.lo `test -f 'src/xmlparse.cc' || echo '$(srcdir)/'`src/xmlparse.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-xmlparse.Tpo $(DEPDIR)/libledger_la-xmlparse.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/xmlparse.cc' object='libledger_la-xmlparse.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-xmlparse.lo `test -f 'src/xmlparse.cc' || echo '$(srcdir)/'`src/xmlparse.cc + +libledger_la-xpath.lo: src/xpath.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-xpath.lo -MD -MP -MF $(DEPDIR)/libledger_la-xpath.Tpo -c -o libledger_la-xpath.lo `test -f 'src/xpath.cc' || echo '$(srcdir)/'`src/xpath.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-xpath.Tpo $(DEPDIR)/libledger_la-xpath.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/xpath.cc' object='libledger_la-xpath.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-reconcile.lo `test -f 'reconcile.cc' || echo '$(srcdir)/'`reconcile.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-xpath.lo `test -f 'src/xpath.cc' || echo '$(srcdir)/'`src/xpath.cc -libledger_la-gnucash.lo: gnucash.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-gnucash.lo -MD -MP -MF $(DEPDIR)/libledger_la-gnucash.Tpo -c -o libledger_la-gnucash.lo `test -f 'gnucash.cc' || echo '$(srcdir)/'`gnucash.cc +libledger_la-gnucash.lo: src/gnucash.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-gnucash.lo -MD -MP -MF $(DEPDIR)/libledger_la-gnucash.Tpo -c -o libledger_la-gnucash.lo `test -f 'src/gnucash.cc' || echo '$(srcdir)/'`src/gnucash.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-gnucash.Tpo $(DEPDIR)/libledger_la-gnucash.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='gnucash.cc' object='libledger_la-gnucash.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/gnucash.cc' object='libledger_la-gnucash.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-gnucash.lo `test -f 'gnucash.cc' || echo '$(srcdir)/'`gnucash.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-gnucash.lo `test -f 'src/gnucash.cc' || echo '$(srcdir)/'`src/gnucash.cc -libledger_la-ofx.lo: ofx.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-ofx.lo -MD -MP -MF $(DEPDIR)/libledger_la-ofx.Tpo -c -o libledger_la-ofx.lo `test -f 'ofx.cc' || echo '$(srcdir)/'`ofx.cc +libledger_la-ofx.lo: src/ofx.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-ofx.lo -MD -MP -MF $(DEPDIR)/libledger_la-ofx.Tpo -c -o libledger_la-ofx.lo `test -f 'src/ofx.cc' || echo '$(srcdir)/'`src/ofx.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-ofx.Tpo $(DEPDIR)/libledger_la-ofx.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='ofx.cc' object='libledger_la-ofx.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/ofx.cc' object='libledger_la-ofx.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-ofx.lo `test -f 'ofx.cc' || echo '$(srcdir)/'`ofx.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-ofx.lo `test -f 'src/ofx.cc' || echo '$(srcdir)/'`src/ofx.cc -libpyledger_la-py_eval.lo: py_eval.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpyledger_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpyledger_la-py_eval.lo -MD -MP -MF $(DEPDIR)/libpyledger_la-py_eval.Tpo -c -o libpyledger_la-py_eval.lo `test -f 'py_eval.cc' || echo '$(srcdir)/'`py_eval.cc -@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libpyledger_la-py_eval.Tpo $(DEPDIR)/libpyledger_la-py_eval.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='py_eval.cc' object='libpyledger_la-py_eval.lo' libtool=yes @AMDEPBACKSLASH@ +libledger_la-pyinterp.lo: src/pyinterp.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -MT libledger_la-pyinterp.lo -MD -MP -MF $(DEPDIR)/libledger_la-pyinterp.Tpo -c -o libledger_la-pyinterp.lo `test -f 'src/pyinterp.cc' || echo '$(srcdir)/'`src/pyinterp.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libledger_la-pyinterp.Tpo $(DEPDIR)/libledger_la-pyinterp.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/pyinterp.cc' object='libledger_la-pyinterp.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpyledger_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpyledger_la-py_eval.lo `test -f 'py_eval.cc' || echo '$(srcdir)/'`py_eval.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libledger_la_CPPFLAGS) $(CPPFLAGS) $(libledger_la_CXXFLAGS) $(CXXFLAGS) -c -o libledger_la-pyinterp.lo `test -f 'src/pyinterp.cc' || echo '$(srcdir)/'`src/pyinterp.cc -libpyledger_la-py_amount.lo: py_amount.cc -@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpyledger_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpyledger_la-py_amount.lo -MD -MP -MF $(DEPDIR)/libpyledger_la-py_amount.Tpo -c -o libpyledger_la-py_amount.lo `test -f 'py_amount.cc' || echo '$(srcdir)/'`py_amount.cc +libpyledger_la-py_amount.lo: src/py_amount.cc +@am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpyledger_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpyledger_la-py_amount.lo -MD -MP -MF $(DEPDIR)/libpyledger_la-py_amount.Tpo -c -o libpyledger_la-py_amount.lo `test -f 'src/py_amount.cc' || echo '$(srcdir)/'`src/py_amount.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libpyledger_la-py_amount.Tpo $(DEPDIR)/libpyledger_la-py_amount.Plo -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='py_amount.cc' object='libpyledger_la-py_amount.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/py_amount.cc' object='libpyledger_la-py_amount.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpyledger_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpyledger_la-py_amount.lo `test -f 'py_amount.cc' || echo '$(srcdir)/'`py_amount.cc +@am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpyledger_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpyledger_la-py_amount.lo `test -f 'src/py_amount.cc' || echo '$(srcdir)/'`src/py_amount.cc UnitTests-UnitTests.o: tests/UnitTests.cc @am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-UnitTests.o -MD -MP -MF $(DEPDIR)/UnitTests-UnitTests.Tpo -c -o UnitTests-UnitTests.o `test -f 'tests/UnitTests.cc' || echo '$(srcdir)/'`tests/UnitTests.cc @@ -837,89 +847,103 @@ UnitTests-UnitTests.obj: tests/UnitTests.cc @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-UnitTests.obj `if test -f 'tests/UnitTests.cc'; then $(CYGPATH_W) 'tests/UnitTests.cc'; else $(CYGPATH_W) '$(srcdir)/tests/UnitTests.cc'; fi` -UnitTests-BasicAmount.o: tests/corelib/numerics/BasicAmount.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-BasicAmount.o -MD -MP -MF $(DEPDIR)/UnitTests-BasicAmount.Tpo -c -o UnitTests-BasicAmount.o `test -f 'tests/corelib/numerics/BasicAmount.cc' || echo '$(srcdir)/'`tests/corelib/numerics/BasicAmount.cc +UnitTests-BasicAmount.o: tests/numerics/BasicAmount.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-BasicAmount.o -MD -MP -MF $(DEPDIR)/UnitTests-BasicAmount.Tpo -c -o UnitTests-BasicAmount.o `test -f 'tests/numerics/BasicAmount.cc' || echo '$(srcdir)/'`tests/numerics/BasicAmount.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-BasicAmount.Tpo $(DEPDIR)/UnitTests-BasicAmount.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/BasicAmount.cc' object='UnitTests-BasicAmount.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/BasicAmount.cc' object='UnitTests-BasicAmount.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-BasicAmount.o `test -f 'tests/corelib/numerics/BasicAmount.cc' || echo '$(srcdir)/'`tests/corelib/numerics/BasicAmount.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-BasicAmount.o `test -f 'tests/numerics/BasicAmount.cc' || echo '$(srcdir)/'`tests/numerics/BasicAmount.cc -UnitTests-BasicAmount.obj: tests/corelib/numerics/BasicAmount.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-BasicAmount.obj -MD -MP -MF $(DEPDIR)/UnitTests-BasicAmount.Tpo -c -o UnitTests-BasicAmount.obj `if test -f 'tests/corelib/numerics/BasicAmount.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/BasicAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/BasicAmount.cc'; fi` +UnitTests-BasicAmount.obj: tests/numerics/BasicAmount.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-BasicAmount.obj -MD -MP -MF $(DEPDIR)/UnitTests-BasicAmount.Tpo -c -o UnitTests-BasicAmount.obj `if test -f 'tests/numerics/BasicAmount.cc'; then $(CYGPATH_W) 'tests/numerics/BasicAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/BasicAmount.cc'; fi` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-BasicAmount.Tpo $(DEPDIR)/UnitTests-BasicAmount.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/BasicAmount.cc' object='UnitTests-BasicAmount.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/BasicAmount.cc' object='UnitTests-BasicAmount.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-BasicAmount.obj `if test -f 'tests/corelib/numerics/BasicAmount.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/BasicAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/BasicAmount.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-BasicAmount.obj `if test -f 'tests/numerics/BasicAmount.cc'; then $(CYGPATH_W) 'tests/numerics/BasicAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/BasicAmount.cc'; fi` -UnitTests-CommodityAmount.o: tests/corelib/numerics/CommodityAmount.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-CommodityAmount.o -MD -MP -MF $(DEPDIR)/UnitTests-CommodityAmount.Tpo -c -o UnitTests-CommodityAmount.o `test -f 'tests/corelib/numerics/CommodityAmount.cc' || echo '$(srcdir)/'`tests/corelib/numerics/CommodityAmount.cc +UnitTests-CommodityAmount.o: tests/numerics/CommodityAmount.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-CommodityAmount.o -MD -MP -MF $(DEPDIR)/UnitTests-CommodityAmount.Tpo -c -o UnitTests-CommodityAmount.o `test -f 'tests/numerics/CommodityAmount.cc' || echo '$(srcdir)/'`tests/numerics/CommodityAmount.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-CommodityAmount.Tpo $(DEPDIR)/UnitTests-CommodityAmount.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/CommodityAmount.cc' object='UnitTests-CommodityAmount.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/CommodityAmount.cc' object='UnitTests-CommodityAmount.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-CommodityAmount.o `test -f 'tests/corelib/numerics/CommodityAmount.cc' || echo '$(srcdir)/'`tests/corelib/numerics/CommodityAmount.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-CommodityAmount.o `test -f 'tests/numerics/CommodityAmount.cc' || echo '$(srcdir)/'`tests/numerics/CommodityAmount.cc -UnitTests-CommodityAmount.obj: tests/corelib/numerics/CommodityAmount.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-CommodityAmount.obj -MD -MP -MF $(DEPDIR)/UnitTests-CommodityAmount.Tpo -c -o UnitTests-CommodityAmount.obj `if test -f 'tests/corelib/numerics/CommodityAmount.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/CommodityAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/CommodityAmount.cc'; fi` +UnitTests-CommodityAmount.obj: tests/numerics/CommodityAmount.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-CommodityAmount.obj -MD -MP -MF $(DEPDIR)/UnitTests-CommodityAmount.Tpo -c -o UnitTests-CommodityAmount.obj `if test -f 'tests/numerics/CommodityAmount.cc'; then $(CYGPATH_W) 'tests/numerics/CommodityAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/CommodityAmount.cc'; fi` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-CommodityAmount.Tpo $(DEPDIR)/UnitTests-CommodityAmount.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/CommodityAmount.cc' object='UnitTests-CommodityAmount.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/CommodityAmount.cc' object='UnitTests-CommodityAmount.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-CommodityAmount.obj `if test -f 'tests/corelib/numerics/CommodityAmount.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/CommodityAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/CommodityAmount.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-CommodityAmount.obj `if test -f 'tests/numerics/CommodityAmount.cc'; then $(CYGPATH_W) 'tests/numerics/CommodityAmount.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/CommodityAmount.cc'; fi` -UnitTests-DateTime.o: tests/corelib/numerics/DateTime.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-DateTime.o -MD -MP -MF $(DEPDIR)/UnitTests-DateTime.Tpo -c -o UnitTests-DateTime.o `test -f 'tests/corelib/numerics/DateTime.cc' || echo '$(srcdir)/'`tests/corelib/numerics/DateTime.cc +UnitTests-DateTime.o: tests/numerics/DateTime.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-DateTime.o -MD -MP -MF $(DEPDIR)/UnitTests-DateTime.Tpo -c -o UnitTests-DateTime.o `test -f 'tests/numerics/DateTime.cc' || echo '$(srcdir)/'`tests/numerics/DateTime.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-DateTime.Tpo $(DEPDIR)/UnitTests-DateTime.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/DateTime.cc' object='UnitTests-DateTime.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/DateTime.cc' object='UnitTests-DateTime.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-DateTime.o `test -f 'tests/corelib/numerics/DateTime.cc' || echo '$(srcdir)/'`tests/corelib/numerics/DateTime.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-DateTime.o `test -f 'tests/numerics/DateTime.cc' || echo '$(srcdir)/'`tests/numerics/DateTime.cc -UnitTests-DateTime.obj: tests/corelib/numerics/DateTime.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-DateTime.obj -MD -MP -MF $(DEPDIR)/UnitTests-DateTime.Tpo -c -o UnitTests-DateTime.obj `if test -f 'tests/corelib/numerics/DateTime.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/DateTime.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/DateTime.cc'; fi` +UnitTests-DateTime.obj: tests/numerics/DateTime.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-DateTime.obj -MD -MP -MF $(DEPDIR)/UnitTests-DateTime.Tpo -c -o UnitTests-DateTime.obj `if test -f 'tests/numerics/DateTime.cc'; then $(CYGPATH_W) 'tests/numerics/DateTime.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/DateTime.cc'; fi` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-DateTime.Tpo $(DEPDIR)/UnitTests-DateTime.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/DateTime.cc' object='UnitTests-DateTime.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/DateTime.cc' object='UnitTests-DateTime.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-DateTime.obj `if test -f 'tests/corelib/numerics/DateTime.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/DateTime.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/DateTime.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-DateTime.obj `if test -f 'tests/numerics/DateTime.cc'; then $(CYGPATH_W) 'tests/numerics/DateTime.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/DateTime.cc'; fi` -UnitTests-Commodity.o: tests/corelib/numerics/Commodity.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-Commodity.o -MD -MP -MF $(DEPDIR)/UnitTests-Commodity.Tpo -c -o UnitTests-Commodity.o `test -f 'tests/corelib/numerics/Commodity.cc' || echo '$(srcdir)/'`tests/corelib/numerics/Commodity.cc +UnitTests-Commodity.o: tests/numerics/Commodity.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-Commodity.o -MD -MP -MF $(DEPDIR)/UnitTests-Commodity.Tpo -c -o UnitTests-Commodity.o `test -f 'tests/numerics/Commodity.cc' || echo '$(srcdir)/'`tests/numerics/Commodity.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-Commodity.Tpo $(DEPDIR)/UnitTests-Commodity.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/Commodity.cc' object='UnitTests-Commodity.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/Commodity.cc' object='UnitTests-Commodity.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-Commodity.o `test -f 'tests/corelib/numerics/Commodity.cc' || echo '$(srcdir)/'`tests/corelib/numerics/Commodity.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-Commodity.o `test -f 'tests/numerics/Commodity.cc' || echo '$(srcdir)/'`tests/numerics/Commodity.cc -UnitTests-Commodity.obj: tests/corelib/numerics/Commodity.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-Commodity.obj -MD -MP -MF $(DEPDIR)/UnitTests-Commodity.Tpo -c -o UnitTests-Commodity.obj `if test -f 'tests/corelib/numerics/Commodity.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/Commodity.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/Commodity.cc'; fi` +UnitTests-Commodity.obj: tests/numerics/Commodity.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UnitTests-Commodity.obj -MD -MP -MF $(DEPDIR)/UnitTests-Commodity.Tpo -c -o UnitTests-Commodity.obj `if test -f 'tests/numerics/Commodity.cc'; then $(CYGPATH_W) 'tests/numerics/Commodity.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/Commodity.cc'; fi` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/UnitTests-Commodity.Tpo $(DEPDIR)/UnitTests-Commodity.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/corelib/numerics/Commodity.cc' object='UnitTests-Commodity.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/numerics/Commodity.cc' object='UnitTests-Commodity.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-Commodity.obj `if test -f 'tests/corelib/numerics/Commodity.cc'; then $(CYGPATH_W) 'tests/corelib/numerics/Commodity.cc'; else $(CYGPATH_W) '$(srcdir)/tests/corelib/numerics/Commodity.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(UnitTests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UnitTests-Commodity.obj `if test -f 'tests/numerics/Commodity.cc'; then $(CYGPATH_W) 'tests/numerics/Commodity.cc'; else $(CYGPATH_W) '$(srcdir)/tests/numerics/Commodity.cc'; fi` -ledger-option.o: option.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-option.o -MD -MP -MF $(DEPDIR)/ledger-option.Tpo -c -o ledger-option.o `test -f 'option.cc' || echo '$(srcdir)/'`option.cc +ledger-option.o: src/option.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-option.o -MD -MP -MF $(DEPDIR)/ledger-option.Tpo -c -o ledger-option.o `test -f 'src/option.cc' || echo '$(srcdir)/'`src/option.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/ledger-option.Tpo $(DEPDIR)/ledger-option.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='option.cc' object='ledger-option.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/option.cc' object='ledger-option.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-option.o `test -f 'option.cc' || echo '$(srcdir)/'`option.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-option.o `test -f 'src/option.cc' || echo '$(srcdir)/'`src/option.cc -ledger-option.obj: option.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-option.obj -MD -MP -MF $(DEPDIR)/ledger-option.Tpo -c -o ledger-option.obj `if test -f 'option.cc'; then $(CYGPATH_W) 'option.cc'; else $(CYGPATH_W) '$(srcdir)/option.cc'; fi` +ledger-option.obj: src/option.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-option.obj -MD -MP -MF $(DEPDIR)/ledger-option.Tpo -c -o ledger-option.obj `if test -f 'src/option.cc'; then $(CYGPATH_W) 'src/option.cc'; else $(CYGPATH_W) '$(srcdir)/src/option.cc'; fi` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/ledger-option.Tpo $(DEPDIR)/ledger-option.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='option.cc' object='ledger-option.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/option.cc' object='ledger-option.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-option.obj `if test -f 'option.cc'; then $(CYGPATH_W) 'option.cc'; else $(CYGPATH_W) '$(srcdir)/option.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-option.obj `if test -f 'src/option.cc'; then $(CYGPATH_W) 'src/option.cc'; else $(CYGPATH_W) '$(srcdir)/src/option.cc'; fi` -ledger-main.o: main.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-main.o -MD -MP -MF $(DEPDIR)/ledger-main.Tpo -c -o ledger-main.o `test -f 'main.cc' || echo '$(srcdir)/'`main.cc +ledger-main.o: src/main.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-main.o -MD -MP -MF $(DEPDIR)/ledger-main.Tpo -c -o ledger-main.o `test -f 'src/main.cc' || echo '$(srcdir)/'`src/main.cc @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/ledger-main.Tpo $(DEPDIR)/ledger-main.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='main.cc' object='ledger-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/main.cc' object='ledger-main.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-main.o `test -f 'main.cc' || echo '$(srcdir)/'`main.cc +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-main.o `test -f 'src/main.cc' || echo '$(srcdir)/'`src/main.cc -ledger-main.obj: main.cc -@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-main.obj -MD -MP -MF $(DEPDIR)/ledger-main.Tpo -c -o ledger-main.obj `if test -f 'main.cc'; then $(CYGPATH_W) 'main.cc'; else $(CYGPATH_W) '$(srcdir)/main.cc'; fi` +ledger-main.obj: src/main.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -MT ledger-main.obj -MD -MP -MF $(DEPDIR)/ledger-main.Tpo -c -o ledger-main.obj `if test -f 'src/main.cc'; then $(CYGPATH_W) 'src/main.cc'; else $(CYGPATH_W) '$(srcdir)/src/main.cc'; fi` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/ledger-main.Tpo $(DEPDIR)/ledger-main.Po -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='main.cc' object='ledger-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/main.cc' object='ledger-main.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-main.obj `if test -f 'main.cc'; then $(CYGPATH_W) 'main.cc'; else $(CYGPATH_W) '$(srcdir)/main.cc'; fi` +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(ledger_CPPFLAGS) $(CPPFLAGS) $(ledger_CXXFLAGS) $(CXXFLAGS) -c -o ledger-main.obj `if test -f 'src/main.cc'; then $(CYGPATH_W) 'src/main.cc'; else $(CYGPATH_W) '$(srcdir)/src/main.cc'; fi` + +pyledger.o: src/pyledger.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT pyledger.o -MD -MP -MF $(DEPDIR)/pyledger.Tpo -c -o pyledger.o `test -f 'src/pyledger.cc' || echo '$(srcdir)/'`src/pyledger.cc +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/pyledger.Tpo $(DEPDIR)/pyledger.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/pyledger.cc' object='pyledger.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o pyledger.o `test -f 'src/pyledger.cc' || echo '$(srcdir)/'`src/pyledger.cc + +pyledger.obj: src/pyledger.cc +@am__fastdepCXX_TRUE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT pyledger.obj -MD -MP -MF $(DEPDIR)/pyledger.Tpo -c -o pyledger.obj `if test -f 'src/pyledger.cc'; then $(CYGPATH_W) 'src/pyledger.cc'; else $(CYGPATH_W) '$(srcdir)/src/pyledger.cc'; fi` +@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/pyledger.Tpo $(DEPDIR)/pyledger.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='src/pyledger.cc' object='pyledger.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o pyledger.obj `if test -f 'src/pyledger.cc'; then $(CYGPATH_W) 'src/pyledger.cc'; else $(CYGPATH_W) '$(srcdir)/src/pyledger.cc'; fi` mostlyclean-libtool: -rm -f *.lo @@ -929,8 +953,11 @@ clean-libtool: distclean-libtool: -rm -f libtool +docs/$(am__dirstamp): + @$(MKDIR_P) docs + @: > docs/$(am__dirstamp) -.texi.info: +$(srcdir)/docs/ledger.info: docs/ledger.texi restore=: && backupdir="$(am__leading_dot)am$$$$" && \ am__cwd=`pwd` && cd $(srcdir) && \ rm -rf $$backupdir && mkdir $$backupdir && \ @@ -940,8 +967,8 @@ distclean-libtool: done; \ else :; fi && \ cd "$$am__cwd"; \ - if $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) \ - -o $@ $<; \ + if $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I docs -I $(srcdir)/docs \ + -o $@ $(srcdir)/docs/ledger.texi; \ then \ rc=0; \ cd $(srcdir); \ @@ -952,20 +979,20 @@ distclean-libtool: fi; \ rm -rf $$backupdir; exit $$rc -.texi.dvi: +docs/ledger.dvi: docs/ledger.texi docs/$(am__dirstamp) TEXINPUTS="$(am__TEXINFO_TEX_DIR)$(PATH_SEPARATOR)$$TEXINPUTS" \ - MAKEINFO='$(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir)' \ - $(TEXI2DVI) $< + MAKEINFO='$(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I docs -I $(srcdir)/docs' \ + $(TEXI2DVI) -o $@ `test -f 'docs/ledger.texi' || echo '$(srcdir)/'`docs/ledger.texi -.texi.pdf: +docs/ledger.pdf: docs/ledger.texi docs/$(am__dirstamp) TEXINPUTS="$(am__TEXINFO_TEX_DIR)$(PATH_SEPARATOR)$$TEXINPUTS" \ - MAKEINFO='$(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir)' \ - $(TEXI2PDF) $< + MAKEINFO='$(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I docs -I $(srcdir)/docs' \ + $(TEXI2PDF) -o $@ `test -f 'docs/ledger.texi' || echo '$(srcdir)/'`docs/ledger.texi -.texi.html: +docs/ledger.html: docs/ledger.texi docs/$(am__dirstamp) rm -rf $(@:.html=.htp) - if $(MAKEINFOHTML) $(AM_MAKEINFOHTMLFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) \ - -o $(@:.html=.htp) $<; \ + if $(MAKEINFOHTML) $(AM_MAKEINFOHTMLFLAGS) $(MAKEINFOFLAGS) -I docs -I $(srcdir)/docs \ + -o $(@:.html=.htp) `test -f 'docs/ledger.texi' || echo '$(srcdir)/'`docs/ledger.texi; \ then \ rm -rf $@; \ if test ! -d $(@:.html=.htp) && test -d $(@:.html=); then \ @@ -975,10 +1002,6 @@ distclean-libtool: rm -rf $(@:.html=); else rm -Rf $(@:.html=.htp) $@; fi; \ exit 1; \ fi -$(srcdir)/ledger.info: ledger.texi -ledger.dvi: ledger.texi -ledger.pdf: ledger.texi -ledger.html: ledger.texi .dvi.ps: TEXINPUTS="$(am__TEXINFO_TEX_DIR)$(PATH_SEPARATOR)$$TEXINPUTS" \ $(DVIPS) -o $@ $< @@ -1060,7 +1083,8 @@ mostlyclean-aminfo: -rm -rf ledger.aux ledger.cp ledger.cps ledger.fn ledger.fns ledger.ky \ ledger.kys ledger.log ledger.pg ledger.pgs ledger.tmp \ ledger.toc ledger.tp ledger.tps ledger.vr ledger.vrs \ - ledger.dvi ledger.pdf ledger.ps ledger.html + docs/ledger.dvi docs/ledger.pdf docs/ledger.ps \ + docs/ledger.html maintainer-clean-aminfo: @list='$(INFO_DEPS)'; for i in $$list; do \ @@ -1525,6 +1549,7 @@ clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -rm -f docs/$(am__dirstamp) -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) maintainer-clean-generic: @@ -1711,14 +1736,15 @@ uninstall-am: uninstall-binPROGRAMS uninstall-dist_lispLISP \ dist-hook: rm -fr `find $(distdir) -name .svn` -@USE_PCH_TRUE@$(top_builddir)/system.hh.gch: $(srcdir)/system.hh acconf.h $(srcdir)/fdstream.hpp +@USE_PCH_TRUE@$(top_builddir)/system.hh.gch: $(srcdir)/src/system.hh acconf.h @USE_PCH_TRUE@ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(libledger_la_CPPFLAGS) \ -@USE_PCH_TRUE@ -o $@ $(srcdir)/system.hh +@USE_PCH_TRUE@ -o $@ $(srcdir)/src/system.hh @HAVE_BOOST_PYTHON_TRUE@clean-local: @HAVE_BOOST_PYTHON_TRUE@ rm -fr build -@HAVE_BOOST_PYTHON_TRUE@ledger.so: pyledger.cc libledger.la gdtoa/libgdtoa.la libpyledger.la +@HAVE_BOOST_PYTHON_TRUE@ledger.so: src/pyledger.cc \ +@HAVE_BOOST_PYTHON_TRUE@ libledger.la gdtoa/libgdtoa.la libpyledger.la @HAVE_BOOST_PYTHON_TRUE@ SRCDIR="$(srcdir)" \ @HAVE_BOOST_PYTHON_TRUE@ CFLAGS="$(CPPFLAGS) -I$(srcdir) $(libledger_la_CPPFLAGS)" \ @HAVE_BOOST_PYTHON_TRUE@ LDFLAGS="$(LDFLAGS) -L. -L.libs -Lgdtoa -Lgdtoa/.libs" \ @@ -1732,15 +1758,17 @@ dist-hook: @HAVE_BOOST_PYTHON_TRUE@ PYLIBS="$(PYLIBS)" \ @HAVE_BOOST_PYTHON_TRUE@ python $(srcdir)/setup.py install --prefix=$(prefix) -PyUnitTests: PyUnitTests.py - cat $(srcdir)/PyUnitTests.py | sed "s/%srcdir%/$(ESC_srcdir)/g" \ - | sed "s/%builddir%/$(ESC_builddir)/g" > PyUnitTests - chmod 755 PyUnitTests +PyUnitTests: $(srcdir)/tests/python/PyUnitTests.py + cat $(srcdir)/tests/python/PyUnitTests.py \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ + | sed "s/%builddir%/$(ESC_builddir)/g" > $@ + chmod 755 $@ -alldocs: ledger.info ledger.pdf doxygen-docs +alldocs: docs/ledger.info docs/ledger.pdf doxygen-docs -$(top_builddir)/Doxyfile.gen: $(srcdir)/Doxyfile - cat $(srcdir)/Doxyfile | sed "s/%srcdir%/$(ESC_srcdir)/g" \ +$(top_builddir)/Doxyfile.gen: $(srcdir)/docs/Doxyfile + cat $(srcdir)/docs/Doxyfile \ + | sed "s/%srcdir%/$(ESC_srcdir)/g" \ | sed "s/%builddir%/$(ESC_builddir)/g" > $@ doxygen-docs: $(top_builddir)/Doxyfile.gen @@ -1762,7 +1790,7 @@ all-clean: maintainer-clean autom4te config.guess config.sub configure depcomp install-sh \ libtool ltconfig ltmain.sh missing stamp texinfo.tex \ Makefile.in mkinstalldirs elisp-comp elc-stamp elc-temp \ - py-compile + py-compile ylwrap compile # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: diff --git a/PyUnitTests.py b/PyUnitTests.py deleted file mode 100755 index 71267798..00000000 --- a/PyUnitTests.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -PYTHONPATH="%builddir%":"%srcdir%":$PYTHONPATH \ - python "%srcdir%"/tests/python/UnitTests.py diff --git a/acprep b/acprep index ee8488ee..45ce5348 100755 --- a/acprep +++ b/acprep @@ -96,6 +96,5 @@ if [ -d "$HOME/Products" ]; then fi "$HERE/configure" --srcdir="$HERE" \ - EMACS="$HOME/bin/emacs" EMACSLOADPATH="$EMACSLOADPATH" \ CPPFLAGS="$INCDIRS" CXXFLAGS="$CXXFLAGS $local_cxxflags" \ LDFLAGS="$LIBDIRS" $SWITCHES "$@" diff --git a/amount.cc b/amount.cc deleted file mode 100644 index ac7bbdb7..00000000 --- a/amount.cc +++ /dev/null @@ -1,2041 +0,0 @@ -/** - * @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 the various - * flavors of commodity_t. - */ - -/* - * Copyright (c) 2003-2007, 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 "amount.h" -#include "binary.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; -bool amount_t::full_strings = 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) { - TRACE_CTOR(bigint_t, ""); - mpz_init(val); - } - bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) { - TRACE_CTOR(bigint_t, "mpz_t"); - mpz_init_set(val, _val); - } - bigint_t(const bigint_t& other) - : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC), - ref(1), index(0) { - TRACE_CTOR(bigint_t, "copy"); - mpz_init_set(val, other.val); - } - ~bigint_t(); -}; - -unsigned int sizeof_bigint_t() { - return sizeof(amount_t::bigint_t); -} - -#define MPZ(x) ((x)->val) - -#ifndef THREADSAFE -static mpz_t temp; // these are the global temp variables -static mpz_t divisor; -#endif - -static amount_t::bigint_t * true_value = NULL; - -inline amount_t::bigint_t::~bigint_t() { - TRACE_DTOR(bigint_t); - assert(ref == 0 || (! do_cleanup && this == true_value)); - mpz_clear(val); -} - -#ifndef THREADSAFE -base_commodities_map commodity_base_t::commodities; - -commodity_base_t::updater_t * commodity_base_t::updater = NULL; - -commodities_map commodity_t::commodities; -commodities_array * commodity_t::commodities_by_ident; -bool commodity_t::commodities_sorted = false; -commodity_t * commodity_t::null_commodity; -commodity_t * commodity_t::default_commodity = NULL; -#endif - -void amount_t::initialize() -{ - mpz_init(temp); - mpz_init(divisor); - - true_value = new amount_t::bigint_t; - mpz_set_ui(true_value->val, 1); - - commodity_base_t::updater = NULL; - - commodity_t::commodities_by_ident = new commodities_array; - - commodity_t::default_commodity = NULL; - commodity_t::null_commodity = commodity_t::create(""); - 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_t::create("s"); - commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); - - parse_conversion("1.0m", "60s"); - parse_conversion("1.0h", "60m"); -} - -void amount_t::shutdown() -{ - mpz_clear(temp); - mpz_clear(divisor); - - if (commodity_base_t::updater) { - delete commodity_base_t::updater; - commodity_base_t::updater = NULL; - } - - for (base_commodities_map::iterator i = commodity_base_t::commodities.begin(); - i != commodity_base_t::commodities.end(); - i++) - delete (*i).second; - - for (commodities_map::iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); - i++) - delete (*i).second; - - commodity_base_t::commodities.clear(); - commodity_t::commodities.clear(); - - delete commodity_t::commodities_by_ident; - commodity_t::commodities_by_ident = NULL; - - commodity_t::null_commodity = NULL; - commodity_t::default_commodity = NULL; - - true_value->ref--; - assert(true_value->ref == 0); - delete true_value; - true_value = NULL; -} - -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 long val) -{ - TRACE_CTOR(amount_t, "const long"); - if (val != 0) { - quantity = new bigint_t; - mpz_set_si(MPZ(quantity), val); - } else { - quantity = NULL; - } - commodity_ = NULL; -} - -amount_t::amount_t(const unsigned long val) -{ - TRACE_CTOR(amount_t, "const unsigned long"); - if (val != 0) { - quantity = new bigint_t; - mpz_set_ui(MPZ(quantity), val); - } else { - quantity = NULL; - } - commodity_ = NULL; -} - -namespace { - unsigned char convert_double(mpz_t dest, double val) - { -#ifndef HAVE_GDTOA - // This code is far too imprecise to be worthwhile. - - mpf_t temp; - mpf_init_set_d(temp, val); - - mp_exp_t exp; - char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp); - - int len = std::strlen(buf); - if (len > 0 && buf[0] == '-') - exp++; - - if (exp <= len) { - exp = len - exp; - } else { - // There were trailing zeros, which we have to put back on in - // order to convert this buffer into an integer. - - int zeroes = exp - len; - - char * newbuf = (char *)std::malloc(len + zeroes); - std::strcpy(newbuf, buf); - - int i; - for (i = 0; i < zeroes; i++) - newbuf[len + i] = '0'; - newbuf[len + i] = '\0'; - - free(buf); - buf = newbuf; - - exp = (len - exp) + zeroes; - } - - mpz_set_str(dest, buf, 10); - free(buf); - - return (unsigned char)exp; -#else - 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]; - - 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) + 1]; - newbuf[0] = '-'; - std::strcpy(&newbuf[1], result ? result : buf); - mpz_set_str(dest, newbuf, 10); - delete[] newbuf; - } else { - mpz_set_str(dest, result ? result : buf, 10); - } - - if (result) - delete[] result; - freedtoa(buf); - - return decpt; -#endif - } -} - -amount_t::amount_t(const double val) -{ - TRACE_CTOR(amount_t, "const double"); - quantity = new bigint_t; - quantity->prec = convert_double(MPZ(quantity), val); - commodity_ = NULL; -} - -void amount_t::_release() -{ - DEBUG_("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_("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } - } - commodity_ = amt.commodity_; -} - -amount_t& amount_t::operator=(const string& val) -{ - std::istringstream str(val); - parse(str); - return *this; -} - -amount_t& amount_t::operator=(const char * val) -{ - string valstr(val); - 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 long val) -{ - if (val == 0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_si(MPZ(quantity), val); - } - return *this; -} - -amount_t& amount_t::operator=(const unsigned long val) -{ - if (val == 0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_ui(MPZ(quantity), val); - } - return *this; -} - -amount_t& amount_t::operator=(const double val) -{ - commodity_ = NULL; - _init(); - quantity->prec = convert_double(MPZ(quantity), val); - 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; -} - - -void amount_t::_clear() -{ - if (quantity) { - _release(); - quantity = NULL; - commodity_ = NULL; - } else { - assert(! commodity_); - } -} - - -amount_t& amount_t::operator+=(const amount_t& amt) -{ - if (commodity() != amt.commodity()) { - throw amount_exception - (string("Adding amounts with different commodities: ") + - (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + - (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), - context()); - } - - if (! amt.quantity) - return *this; - - if (! quantity) { - _copy(amt); - return *this; - } - - _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) -{ - if (commodity() != amt.commodity()) - throw amount_exception - (string("Subtracting amounts with different commodities: ") + - (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + - (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), - context()); - - 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 (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; -} - -amount_t& amount_t::operator*=(const amount_t& amt) -{ - if (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) { - throw amount_exception - (string("Multiplying amounts with different commodities: ") + - (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + - (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), - context()); - } - - if (! amt.quantity) { - *this = *this - *this; // preserve our commodity - goto finish; - } - else if (! quantity) { - *this = amt; - *this = *this - *this; // preserve the foreign commodity - goto finish; - } - - _dup(); - - mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += amt.quantity->prec; - - finish: - if (! has_commodity()) - commodity_ = amt.commodity_; - - if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_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 (has_commodity() && amt.has_commodity() && - commodity() != amt.commodity()) { - throw amount_exception - (string("Dividing amounts with different commodities: ") + - (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + - (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), - context()); - } - - if (! amt.quantity || ! amt) { - throw amount_exception("Divide by zero", context()); - } - else if (! quantity) { - *this = amt; - *this = *this - *this; // preserve the foreign commodity - goto finish; - } - - _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; - - finish: - 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->flags & BIGINT_KEEP_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; -} - -// unary negation -void amount_t::in_place_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 (has_commodity() && amt.commodity() && commodity() != amt.commodity()) - throw amount_exception - (string("Cannot compare amounts with different commodities: ") + - commodity().symbol() + " and " + amt.commodity().symbol(), - context()); - - 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)); - } -} - -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; -} - -bool amount_t::zero() const -{ - if (! quantity) - return true; - - if (has_commodity()) { - if (quantity->prec <= commodity().precision()) - return realzero(); - else - return round(commodity().precision()).sign() == 0; - } - return realzero(); -} - -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()); -} - -amount_t amount_t::value(const moment_t& moment) const -{ - if (quantity) { - amount_t amt(commodity().value(moment)); - if (! amt.realzero()) - return (amt * number()).round(); - } - return *this; -} - -amount_t amount_t::round(unsigned int prec) const -{ - amount_t t = *this; - - if (! quantity || quantity->prec <= prec) { - if (quantity && quantity->flags & BIGINT_KEEP_PREC) { - t._dup(); - t.quantity->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->flags &= ~BIGINT_KEEP_PREC; - - return t; -} - -amount_t amount_t::unround() const -{ - if (! quantity) { - amount_t t(0L); - assert(t.quantity); - t.quantity->flags |= BIGINT_KEEP_PREC; - return t; - } - else if (quantity->flags & BIGINT_KEEP_PREC) { - return *this; - } - - amount_t t = *this; - t._dup(); - t.quantity->flags |= BIGINT_KEEP_PREC; - - return t; -} - -void amount_t::print_quantity(std::ostream& out) const -{ - if (! quantity) { - out << "0"; - return; - } - - 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) { - out << "0"; - return; - } - - 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); -} - -void amount_t::print(std::ostream& _out, bool omit_commodity, - bool full_precision) const -{ - amount_t base(*this); - if (! amount_t::keep_base && commodity().larger()) { - amount_t last(*this); - while (last.commodity().larger()) { - last /= last.commodity().larger()->number(); - last.commodity_ = last.commodity().larger()->commodity_; - if (last.abs() < 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 = 0; - - if (quantity) { - if (! comm || full_precision || 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 (! omit_commodity && ! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { - comm.write(out); - - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; - } - - if (negative) - out << "-"; - - if (! quantity || 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 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::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 (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()) { - out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - out << ender; - } - } - - if (! omit_commodity && 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 (! omit_commodity && comm.annotated) { - annotated_commodity_t& ann(static_cast(comm)); - assert(&ann.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(); - - return; -} - -static 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; -} - -// 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, -}; - -static void parse_commodity(std::istream& in, 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 amount_exception("Quoted commodity symbol lacks closing quote", - context()); - } else { - READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); - } - symbol = buf; -} - -bool parse_annotations(std::istream& in, amount_t& price, - moment_t& date, string& tag) -{ - bool has_date = false; - - do { - char buf[256]; - char c = peek_next_nonws(in); - if (c == '{') { - if (price) - throw amount_exception("Commodity specifies more than one price", - context()); - - in.get(c); - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') - in.get(c); - else - throw amount_exception("Commodity price lacks closing brace", context()); - - price.parse(buf, AMOUNT_PARSE_NO_MIGRATE); - price.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 (price.has_commodity() && - price.quantity->prec < price.commodity().precision()) - price = price.round(); // no need to retain individual precision - } - else if (c == '[') { - if (is_valid_moment(date)) - throw amount_exception("Commodity specifies more than one date", - context()); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ']'); - if (c == ']') - in.get(c); - else - throw amount_exception("Commodity date lacks closing bracket", - context()); - - date = parse_datetime(buf); - has_date = true; - } - else if (c == '(') { - if (! tag.empty()) - throw amount_exception("Commodity specifies more than one tag", - context()); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') - in.get(c); - else - throw amount_exception("Commodity tag lacks closing parenthesis", - context()); - - tag = buf; - } - else { - break; - } - } while (true); - - DEBUG_("amounts.commodities", - "Parsed commodity annotations: " - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); - - return has_date; -} - -void amount_t::parse(std::istream& in, unsigned char flags) -{ - // The possible syntax for an amount is: - // - // [-]NUM[ ]SYM [@ AMOUNT] - // SYM[ ][-]NUM [@ AMOUNT] - - string symbol; - string quant; - amount_t tprice; - moment_t tdate; - bool had_date = false; - 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')) - had_date = parse_annotations(in, tprice, tdate, 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')) - had_date = parse_annotations(in, tprice, tdate, tag); - } - } - - if (quant.empty()) - throw amount_exception("No quantity specified for amount", - context()); - - _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_ = NULL; - } else { - commodity_ = commodity_t::find(symbol); - if (! commodity_) { - commodity_ = commodity_t::create(symbol); - newly_created = true; - } - assert(commodity_); - - if (! tprice.realzero() || had_date || ! tag.empty()) - commodity_ = - annotated_commodity_t::find_or_create(*commodity_, tprice, tdate, tag); - } - - // 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().flags() & COMMODITY_STYLE_EUROPEAN) { - quantity->prec = quant.length() - last_comma - 1; - } - else if (last_period != 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 (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { - commodity().add_flags(comm_flags); - if (quantity->prec > commodity().precision()) - commodity().set_precision(quantity->prec); - } - - if (flags & AMOUNT_PARSE_NO_MIGRATE) - quantity->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); - 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(); -} - -void amount_t::in_place_reduce() -{ - while (commodity_ && commodity().smaller()) { - *this *= commodity().smaller()->number(); - commodity_ = commodity().smaller()->commodity_; - } -} - -void parse_conversion(const string& larger_str, - const 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.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::read(std::istream& in) -{ - commodity_t::ident_t ident; - read_binary_long(in, ident); - if (ident == 0xffffffff) - commodity_ = NULL; - else if (ident == 0) - commodity_ = commodity_t::null_commodity; - else - commodity_ = (*commodity_t::commodities_by_ident)[ident - 1]; - - read_quantity(in); -} - -void amount_t::read(char *& data) -{ - commodity_t::ident_t ident; - read_binary_long(data, ident); - if (ident == 0xffffffff) - commodity_ = NULL; - else if (ident == 0) - commodity_ = commodity_t::null_commodity; - else - commodity_ = (*commodity_t::commodities_by_ident)[ident - 1]; - - read_quantity(data); -} - -void amount_t::write(std::ostream& out) const -{ - if (commodity_) - write_binary_long(out, commodity_->ident); - else - write_binary_long(out, 0xffffffff); - - write_quantity(out); -} - - -#ifndef THREADSAFE -static char * bigints; -static char * bigints_next; -static unsigned int bigints_index; -static unsigned int bigints_count; -#endif - -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_("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } -} - -#ifndef THREADSAFE -static char buf[4096]; -#endif - -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_("ledger.validate", "amount_t: quantity->ref == 0"); - return false; - } - } - else if (commodity_) { - DEBUG_("ledger.validate", "amount_t: commodity_ != NULL"); - return false; - } - return true; -} - -void amount_t::annotate_commodity(const amount_t& tprice, - const moment_t& tdate, - const string& tag) -{ - const commodity_t * this_base; - annotated_commodity_t * this_ann = NULL; - - if (commodity().annotated) { - this_ann = &static_cast(commodity()); - this_base = this_ann->ptr; - } else { - this_base = &commodity(); - } - assert(this_base); - - DEBUG_("amounts.commodities", "Annotating commodity for amount " - << *this << std::endl - << " price " << tprice << " " - << " date " << tdate << " " - << " tag " << tag); - - commodity_t * ann_comm = - annotated_commodity_t::find_or_create - (*this_base, ! tprice && this_ann ? this_ann->price : tprice, - ! is_valid_moment(tdate) && this_ann ? this_ann->date : tdate, - tag.empty() && this_ann ? this_ann->tag : tag); - if (ann_comm) - set_commodity(*ann_comm); - - DEBUG_("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_("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(commodity())); - assert(ann_comm.base); - - commodity_t * new_comm; - - if ((_keep_price && ann_comm.price) || - (_keep_date && is_valid_moment(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 : moment_t(), - _keep_tag ? ann_comm.tag : ""); - } else { - new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); - } - assert(new_comm); - - amount_t t(*this); - t.set_commodity(*new_comm); - DEBUG_("amounts.commodities", " Reduced amount is " << t); - - return t; -} - -amount_t amount_t::price() const -{ - if (commodity_ && commodity_->annotated) { - amount_t t(((annotated_commodity_t *)commodity_)->price); - t *= number(); - DEBUG_("amounts.commodities", - "Returning price of " << *this << " = " << t); - return t; - } - return *this; -} - -moment_t amount_t::date() const -{ - if (commodity_ && commodity_->annotated) { - DEBUG_("amounts.commodities", - "Returning date of " << *this << " = " - << ((annotated_commodity_t *)commodity_)->date); - return ((annotated_commodity_t *)commodity_)->date; - } - return moment_t(); -} - - -void commodity_base_t::add_price(const moment_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 result - = history->prices.insert(history_pair(date, price)); - assert(result.second); - } -} - -bool commodity_base_t::remove_price(const moment_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 string& symbol) -{ - commodity_base_t * commodity = new commodity_base_t(symbol); - - DEBUG_("amounts.commodities", "Creating base commodity " << symbol); - - std::pair result - = commodities.insert(base_commodities_pair(symbol, commodity)); - assert(result.second); - - return commodity; -} - -bool commodity_t::needs_quotes(const 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_("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; -} - -commodity_t * commodity_t::create(const string& symbol) -{ - std::auto_ptr 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_("amounts.commodities", - "Creating commodity " << commodity->qualified_symbol); - - std::pair result - = commodities.insert(commodities_pair(symbol, commodity.get())); - if (! result.second) - return NULL; - - commodity->ident = commodities_by_ident->size(); - commodities_by_ident->push_back(commodity.get()); - - // 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 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_t::find(const string& symbol) -{ - DEBUG_("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 moment_t& moment) -{ - moment_t age; - amount_t price; - - if (history) { - assert(history->prices.size() > 0); - - if (! is_valid_moment(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 = moment_t(); - } - } else { - price = (*i).second; - } - } - } - } - - if (updater && ! (flags & COMMODITY_STYLE_NOMARKET)) - (*updater)(*this, moment, age, - (history && history->prices.size() > 0 ? - (*history->prices.rbegin()).first : moment_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(comm).price)) - return false; - - if (is_valid_moment(date) && - (! comm.annotated || - date != static_cast(comm).date)) - return false; - - if (! tag.empty() && - (! comm.annotated || - tag != static_cast(comm).tag)) - return false; - - return true; -} - -void -annotated_commodity_t::write_annotations(std::ostream& out, - const amount_t& price, - const moment_t& date, - const string& tag) -{ - if (price) - out << " {" << price << '}'; - - if (is_valid_moment(date)) - out << " [" << date << ']'; - - if (! tag.empty()) - out << " (" << tag << ')'; -} - -commodity_t * -annotated_commodity_t::create(const commodity_t& comm, - const amount_t& price, - const moment_t& date, - const string& tag, - const string& mapping_key) -{ - std::auto_ptr 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_("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 result - = commodities.insert(commodities_pair(mapping_key, commodity.get())); - if (! result.second) - return NULL; - - commodity->ident = commodities_by_ident->size(); - commodities_by_ident->push_back(commodity.get()); - - return commodity.release(); -} - -namespace { - string make_qualified_name(const commodity_t& comm, - const amount_t& price, - const moment_t& date, - const string& tag) - { - if (price < 0) - throw amount_exception("A commodity's price may not be negative", - context()); - - std::ostringstream name; - - comm.write(name); - annotated_commodity_t::write_annotations(name, price, date, tag); - - DEBUG_("amounts.commodities", "make_qualified_name for " - << comm.qualified_symbol << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); - - DEBUG_("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 moment_t& date, - const string& tag) -{ - 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(leftcomm)); - annotated_commodity_t& arightcomm(static_cast(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.in_place_reduce(); - amount_t rightprice(arightcomm.price); - rightprice.in_place_reduce(); - - if (leftprice.commodity() == rightprice.commodity()) { - amount_t val = leftprice - rightprice; - if (val) - return val < 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(); - - amount_t val = leftprice - rightprice; - if (val) - return val < 0; - } - } - - if (! is_valid_moment(aleftcomm.date) && - is_valid_moment(arightcomm.date)) - return true; - if (is_valid_moment(aleftcomm.date) && - ! is_valid_moment(arightcomm.date)) - return false; - - if (is_valid_moment(aleftcomm.date) && - is_valid_moment(arightcomm.date)) { - duration_t diff = aleftcomm.date - arightcomm.date; - return diff.is_negative(); - } - - 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; - - assert(0); - return true; - } -} - -} // namespace ledger diff --git a/amount.h b/amount.h deleted file mode 100644 index dcd30b8d..00000000 --- a/amount.h +++ /dev/null @@ -1,760 +0,0 @@ -/** - * @file amount.h - * @author John Wiegley - * @date Wed Apr 18 22:05:53 2007 - * - * @brief Types for handling commoditized math. - * - * This file contains two of the most basic types in Ledger: amount_t - * commodity_t, and annotated_commodity_t. Both the commodity types - * share a common base class, commodity_base_t. These four class - * together allow Ledger to handle mathematical expressions involving - * differing commodities, or in some cases math using no commodities - * at all (such as increasing a dollar amount by a multiplier). - */ - -/* - * Copyright (c) 2003-2007, 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 _AMOUNT_H -#define _AMOUNT_H - -#include "utils.h" -#include "times.h" - -namespace ledger { - -extern bool do_cleanup; - -class commodity_t; - -/** - * @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. - * Internally, precision is always kept to an excessive degree. - */ - -class amount_t -{ - public: - class bigint_t; - - static void initialize(); - static void shutdown(); - - static bool keep_price; - static bool keep_date; - static bool keep_tag; - static bool keep_base; - static bool full_strings; - - protected: - void _init(); - void _copy(const amount_t& amt); - void _release(); - void _dup(); - void _resize(unsigned int prec); - void _clear(); - - bigint_t * quantity; - commodity_t * commodity_; - - public: - // constructors - amount_t() : quantity(NULL), commodity_(NULL) { - TRACE_CTOR(amount_t, ""); - } - amount_t(const amount_t& amt) : quantity(NULL) { - TRACE_CTOR(amount_t, "copy"); - if (amt.quantity) - _copy(amt); - else - commodity_ = NULL; - } - amount_t(const string& val) : quantity(NULL) { - TRACE_CTOR(amount_t, "const string&"); - parse(val); - } - amount_t(const char * val) : quantity(NULL) { - TRACE_CTOR(amount_t, "const char *"); - parse(val); - } - amount_t(const long val); - amount_t(const unsigned long val); - amount_t(const double val); - - // destructor - ~amount_t() { - TRACE_DTOR(amount_t); - if (quantity) - _release(); - } - - amount_t number() const { - amount_t temp(*this); - temp.clear_commodity(); - return temp; - } - - bool has_commodity() const; - commodity_t& commodity() const; - void set_commodity(commodity_t& comm) { - commodity_ = &comm; - } - void annotate_commodity(const amount_t& price, - const moment_t& date = moment_t(), - const 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; - moment_t date() const; - - bool null() const { - return ! quantity && ! has_commodity(); - } - - // assignment operator - amount_t& operator=(const amount_t& amt); - amount_t& operator=(const string& val); - amount_t& operator=(const char * val); - amount_t& operator=(const long val); - amount_t& operator=(const unsigned long val); - amount_t& operator=(const double val); - - // 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 - amount_t& operator+=(T val) { - return *this += amount_t(val); - } - template - amount_t& operator-=(T val) { - return *this -= amount_t(val); - } - template - amount_t& operator*=(T val) { - return *this *= amount_t(val); - } - template - amount_t& operator/=(T val) { - return *this /= amount_t(val); - } - - // 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 - amount_t operator+(T val) const { - amount_t temp = *this; - temp += val; - return temp; - } - template - amount_t operator-(T val) const { - amount_t temp = *this; - temp -= val; - return temp; - } - template - amount_t operator*(T val) const { - amount_t temp = *this; - temp *= val; - return temp; - } - template - amount_t operator/(T val) const { - amount_t temp = *this; - temp /= val; - return temp; - } - - // unary negation - void in_place_negate(); - amount_t negate() const { - amount_t temp = *this; - temp.in_place_negate(); - return temp; - } - amount_t operator-() const { - return negate(); - } - - // test for zero and non-zero - int sign() const; - bool zero() const; - bool realzero() const { - return sign() == 0; - } - operator bool() const { - return ! zero(); - } - operator string() const { - return to_string(); - } - - operator long() const; - operator double() const; - - string to_string() const; - string to_fullstring() const; - string quantity_string() 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 - void parse_num(T num) { - std::ostringstream temp; - temp << num; - std::istringstream in(temp.str()); - parse(in); - } - - // POD comparisons -#define AMOUNT_CMP_INT(OP) \ - template \ - 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 - bool operator!=(T num) const { - return ! (*this == num); - } - - amount_t value(const moment_t& moment) const; - - amount_t abs() const { - if (*this < 0) - return negate(); - return *this; - } - - void in_place_reduce(); - amount_t reduce() const { - amount_t temp(*this); - temp.in_place_reduce(); - return temp; - } - - bool valid() const; - - static amount_t exact(const string& value); - - // 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 bool parse_annotations(std::istream& in, amount_t& price, - moment_t& date, string& tag); - - // Streaming interface - - void dump(std::ostream& out) const { - out << "AMOUNT("; - print(out); - out << ")"; - } - -#define AMOUNT_PARSE_NO_MIGRATE 0x01 -#define AMOUNT_PARSE_NO_REDUCE 0x02 - - void print(std::ostream& out, bool omit_commodity = false, - bool full_precision = false) const; - void parse(std::istream& in, unsigned char flags = 0); - void parse(const string& str, unsigned char flags = 0) { - std::istringstream stream(str); - parse(stream, flags); - } - - void print_quantity(std::ostream& out) const; - - void write(std::ostream& out) const; - void read(std::istream& in); - void read(char *& data); - - void write_quantity(std::ostream& out) const; - void read_quantity(std::istream& in); - void read_quantity(char *& data); -}; - -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(); -} - -#define DEFINE_AMOUNT_OPERATORS(T) \ -inline amount_t operator+(const T val, const amount_t& amt) { \ - amount_t temp(val); \ - temp += amt; \ - return temp; \ -} \ -inline amount_t operator-(const T val, const amount_t& amt) { \ - amount_t temp(val); \ - temp -= amt; \ - return temp; \ -} \ -inline amount_t operator*(const T val, const amount_t& amt) { \ - amount_t temp(val); \ - temp *= amt; \ - return temp; \ -} \ -inline amount_t operator/(const T val, const amount_t& amt) { \ - amount_t temp(val); \ - temp /= amt; \ - return temp; \ -} \ - \ -inline bool operator<(const T val, const amount_t& amt) { \ - return amount_t(val) < amt; \ -} \ -inline bool operator<=(const T val, const amount_t& amt) { \ - return amount_t(val) <= amt; \ -} \ -inline bool operator>(const T val, const amount_t& amt) { \ - return amount_t(val) > amt; \ -} \ -inline bool operator>=(const T val, const amount_t& amt) { \ - return amount_t(val) >= amt; \ -} \ -inline bool operator==(const T val, const amount_t& amt) { \ - return amount_t(val) == amt; \ -} \ -inline bool operator!=(const T val, const amount_t& amt) { \ - return amount_t(val) != amt; \ -} - -DEFINE_AMOUNT_OPERATORS(long) -DEFINE_AMOUNT_OPERATORS(unsigned long) -DEFINE_AMOUNT_OPERATORS(double) - -inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { - amt.print(out, false, amount_t::full_strings); - return out; -} -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 history_map; -typedef std::pair history_pair; - -class commodity_base_t; - -typedef std::map base_commodities_map; -typedef std::pair 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; - string name; - 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) { - TRACE_CTOR(commodity_base_t, ""); - } - - commodity_base_t(const commodity_base_t&) { - TRACE_CTOR(commodity_base_t, "copy"); - assert(0); - } - - commodity_base_t(const string& _symbol, - unsigned int _precision = 0, - unsigned int _flags = COMMODITY_STYLE_DEFAULTS) - : precision(_precision), flags(_flags), - smaller(NULL), larger(NULL), symbol(_symbol), history(NULL) { - TRACE_CTOR(commodity_base_t, "const string&, unsigned int, unsigned int"); - } - - ~commodity_base_t() { - TRACE_DTOR(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 string& symbol); - - string symbol; - - struct history_t { - history_map prices; - ptime last_lookup; - history_t() : last_lookup() {} - }; - history_t * history; - - void add_price(const moment_t& date, const amount_t& price); - bool remove_price(const moment_t& date); - amount_t value(const moment_t& moment = now); - - class updater_t { - public: - virtual ~updater_t() {} - virtual void operator()(commodity_base_t& commodity, - const moment_t& moment, - const moment_t& date, - const moment_t& last, - amount_t& price) = 0; - }; - friend class updater_t; - - static updater_t * updater; -}; - -typedef std::map commodities_map; -typedef std::pair commodities_pair; - -typedef std::vector commodities_array; - -class commodity_t -{ - friend class annotated_commodity_t; - - public: - // This map remembers all commodities that have been defined. - - static commodities_map commodities; - static commodities_array * commodities_by_ident; - static bool commodities_sorted; - static commodity_t * null_commodity; - static commodity_t * default_commodity; - - static commodity_t * create(const string& symbol); - static commodity_t * find(const string& name); - static commodity_t * find_or_create(const string& symbol); - - static bool needs_quotes(const string& symbol); - - static void make_alias(const string& symbol, - commodity_t * commodity); - - // These are specific to each commodity reference - - typedef unsigned long ident_t; - - ident_t ident; - commodity_base_t * base; - string qualified_symbol; - bool annotated; - - public: - explicit commodity_t() : base(NULL), annotated(false) { - TRACE_CTOR(commodity_t, ""); - } - commodity_t(const commodity_t& o) - : ident(o.ident), base(o.base), - qualified_symbol(o.qualified_symbol), annotated(o.annotated) { - TRACE_CTOR(commodity_t, "copy"); - } - virtual ~commodity_t() { - TRACE_DTOR(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); - } - - string base_symbol() const { - return base->symbol; - } - string symbol() const { - return qualified_symbol; - } - - void write(std::ostream& out) const { - out << symbol(); - } - - string name() const { - return base->name; - } - void set_name(const string& arg) { - base->name = arg; - } - - string note() const { - return base->note; - } - void set_note(const 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 moment_t& date, const amount_t& price) { - return base->add_price(date, price); - } - bool remove_price(const moment_t& date) { - return base->remove_price(date); - } - amount_t value(const moment_t& moment = now) const { - return base->value(moment); - } - - bool valid() const; -}; - -class annotated_commodity_t : public commodity_t -{ - public: - const commodity_t * ptr; - - amount_t price; - moment_t date; - string tag; - - explicit annotated_commodity_t() { - TRACE_CTOR(annotated_commodity_t, ""); - annotated = true; - } - virtual ~annotated_commodity_t() { - TRACE_DTOR(annotated_commodity_t); - } - - 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 moment_t& date, - const string& tag); - - private: - static commodity_t * create(const commodity_t& comm, - const amount_t& price, - const moment_t& date, - const string& tag, - const string& mapping_key); - - static commodity_t * find_or_create(const commodity_t& comm, - const amount_t& price, - const moment_t& date, - const 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 bool amount_t::has_commodity() const { - return commodity_ && commodity_ != commodity_t::null_commodity; -} - -inline commodity_t& amount_t::commodity() const { - if (! commodity_) - return *commodity_t::null_commodity; - else - return *commodity_; -} - -void parse_conversion(const string& larger_str, - const string& smaller_str); - -DECLARE_EXCEPTION(amount_exception); - -struct compare_amount_commodities { - bool operator()(const amount_t * left, const amount_t * right) const; -}; - -} // namespace ledger - -#endif // _AMOUNT_H diff --git a/balance.cc b/balance.cc deleted file mode 100644 index 487f749f..00000000 --- a/balance.cc +++ /dev/null @@ -1,313 +0,0 @@ -#include "balance.h" - -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); - - throw_(amount_exception, - "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 amount_t(); -} - -balance_t balance_t::value(const moment_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; -} - -moment_t balance_t::date() const -{ - moment_t temp; - - for (amounts_map::const_iterator i = amounts.begin(); - i != amounts.end(); - i++) { - moment_t tdate = (*i).second.date(); - if (! is_valid_moment(temp) && is_valid_moment(tdate)) - temp = tdate; - else if (temp != tdate) - return moment_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::vector amounts_array; - amounts_array 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_array::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 amount_exception(errmsg.str(), context()); - } -} - -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 amount_exception(errmsg.str(), context()); - } - } - 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 amount_exception(errmsg.str(), context()); - } - 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 amount_exception(errmsg.str(), context()); - } -} - -balance_t& balance_t::operator/=(const amount_t& amt) -{ - if (amt.realzero()) { - std::ostringstream errmsg; - errmsg << "Attempt to divide by zero: " << *this << " / " << amt; - throw amount_exception(errmsg.str(), context()); - } - 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 amount_exception(errmsg.str(), context()); - } - } - 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; - - throw_(amount_exception, - "Cannot convert a balance with " << - "multiple commodities to an amount: " << temp); - } -} - -} // namespace ledger diff --git a/balance.h b/balance.h deleted file mode 100644 index 2a6f3072..00000000 --- a/balance.h +++ /dev/null @@ -1,969 +0,0 @@ -#ifndef _BALANCE_H -#define _BALANCE_H - -#include "amount.h" - -namespace ledger { - -typedef std::map amounts_map; -typedef std::pair 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() { - TRACE_CTOR(balance_t, ""); - } - balance_t(const balance_t& bal) { - TRACE_CTOR(balance_t, "copy"); - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; - } - balance_t(const amount_t& amt) { - TRACE_CTOR(balance_t, "const amount_t&"); - if (! amt.realzero()) - amounts.insert(amounts_pair(&amt.commodity(), amt)); - } - template - balance_t(T val) { - TRACE_CTOR(balance_t, "T"); - amount_t amt(val); - if (! amt.realzero()) - amounts.insert(amounts_pair(&amt.commodity(), amt)); - } - - ~balance_t() { - TRACE_DTOR(balance_t); - } - - // 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 - balance_t& operator=(T val) { - amounts.clear(); - *this += val; - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - bool operator!=(T val) const { - return ! (*this == val); - } - - // unary negation - void in_place_negate() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second = (*i).second.negate(); - } - balance_t negate() const { - balance_t temp = *this; - temp.in_place_negate(); - return temp; - } - balance_t operator-() const { - return negate(); - } - - // 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 moment_t& moment = now) const; - balance_t price() const; - moment_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 in_place_abs() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second = (*i).second.abs(); - } - balance_t abs() const { - balance_t temp = *this; - temp.in_place_abs(); - return temp; - } - - void in_place_reduce() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second.in_place_reduce(); - } - balance_t reduce() const { - balance_t temp(*this); - temp.in_place_reduce(); - return temp; - } - - void in_place_round() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second = (*i).second.round(); - } - balance_t round() const { - balance_t temp(*this); - temp.in_place_round(); - return temp; - } - - 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 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) { - TRACE_CTOR(balance_pair_t, ""); - } - balance_pair_t(const balance_pair_t& bal_pair) - : quantity(bal_pair.quantity), cost(NULL) { - TRACE_CTOR(balance_pair_t, "copy"); - if (bal_pair.cost) - cost = new balance_t(*bal_pair.cost); - } - balance_pair_t(const balance_t& _quantity) - : quantity(_quantity), cost(NULL) { - TRACE_CTOR(balance_pair_t, "const balance_t&"); - } - balance_pair_t(const amount_t& _quantity) - : quantity(_quantity), cost(NULL) { - TRACE_CTOR(balance_pair_t, "const amount_t&"); - } - template - balance_pair_t(T val) : quantity(val), cost(NULL) { - TRACE_CTOR(balance_pair_t, "T"); - } - - // destructor - ~balance_pair_t() { - TRACE_DTOR(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 - balance_pair_t& operator=(T val) { - if (cost) { - delete cost; - cost = NULL; - } - quantity = val; - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - bool operator!=(T val) const { - return ! (*this == val); - } - - // unary negation - void in_place_negate() { - quantity = quantity.negate(); - if (cost) - *cost = cost->negate(); - } - balance_pair_t negate() const { - balance_pair_t temp = *this; - temp.in_place_negate(); - return temp; - } - balance_pair_t operator-() const { - return negate(); - } - - // 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 in_place_abs() { - quantity = quantity.abs(); - if (cost) - *cost = cost->abs(); - } - balance_pair_t abs() const { - balance_pair_t temp = *this; - temp.in_place_abs(); - return temp; - } - - amount_t amount(const commodity_t& commodity = - *commodity_t::null_commodity) const { - return quantity.amount(commodity); - } - balance_t value(const moment_t& moment = now) const { - return quantity.value(moment); - } - balance_t price() const { - return quantity.price(); - } - moment_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& amt, - const amount_t * a_cost = NULL) { - if (a_cost && ! cost) - cost = new balance_t(quantity); - quantity += amt; - if (cost) - *cost += a_cost ? *a_cost : amt; - return *this; - } - - bool valid() { - return quantity.valid() && (! cost || cost->valid()); - } - - void in_place_reduce() { - quantity.in_place_reduce(); - if (cost) cost->in_place_reduce(); - } - balance_pair_t reduce() const { - balance_pair_t temp(*this); - temp.in_place_reduce(); - return temp; - } - - void in_place_round() { - quantity = quantity.round(); - if (cost) - *cost = cost->round(); - } - balance_pair_t round() const { - balance_pair_t temp(*this); - temp.in_place_round(); - return temp; - } - - balance_pair_t unround() { - balance_pair_t temp(quantity.unround()); - if (cost) - temp.cost = new balance_t(cost->unround()); - 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 d3238b5a..00000000 --- a/binary.cc +++ /dev/null @@ -1,1013 +0,0 @@ -#include "binary.h" - -namespace ledger { - -#if 0 -static unsigned long binary_magic_number = 0xFFEED765; -#if defined(DEBUG_ON) -static unsigned long format_version = 0x00030000; -#else -static unsigned long format_version = 0x00030000; -#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; -#endif - -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); -} - -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); -} - -void read_binary_string(std::istream& in, 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); -} - -void read_binary_string(char *& data, 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 = string(data, slen); - data += slen; - } - else if (len) { - str = string(data, len); - data += len; - } - else { - str = ""; - } - - read_binary_guard(data, 0x3002); -} - -void read_binary_string(char *& data, 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) string(data, slen); - data += slen; - } - else if (len) { - new(str) string(data, len); - data += len; - } - else { - new(str) string(""); - } - - read_binary_guard(data, 0x3002); -} - -#if 0 -inline void read_binary_value(char *& data, value_t& val) -{ - val.type = static_cast(read_binary_long(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, *((moment_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); - string pattern; - read_binary_string(data, pattern); - - mask = new mask_t(pattern); - mask->exclude = exclude; -} - -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(data) - 1]; - - unsigned char flag = read_binary_number(data); - if (flag == 0) { - read_binary_amount(data, xact->amount); - } - else if (flag == 1) { - string expr; - read_binary_string(data, expr); - xact->amount_expr = expr; - - repitem_t * item = - repitem_t::wrap(xact, static_cast(xact->entry->data)); - xact->data = item; - - xact->amount = valexpr_t(xact->amount_expr).calc(item).to_amount(); - } - - 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(data); - read_binary_long(data, xact->beg_line); - xact->end_pos = read_binary_long(data); - read_binary_long(data, xact->end_line); - - xact->data = NULL; -} - -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(data); - read_binary_long(data, entry->beg_line); - entry->end_pos = read_binary_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(data); - i < count; - i++) { - new(xact_pool) transaction_t; - xact_pool->entry = static_cast(entry); - 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) -{ - entry->data = - repitem_t::wrap(entry, static_cast(entry->journal->data)); - - 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); - - string pred_str; - read_binary_string(data, &pred_str); - entry->predicate.parse(pred_str); -} - -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(data); - i < count; - i++) { - moment_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(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(data) - 1]; - - read_binary_string(data, commodity->qualified_symbol); - commodity->annotated = true; - - commodity->ptr = - commodities[read_binary_long(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(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, - journal_t * journal, - account_t * master, - const string& original_file) -{ - 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 (! original_file.empty()) { - for (unsigned short i = 0, - count = read_binary_number(in); - i < count; - i++) { - 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(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(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(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(data); - unsigned long auto_count = read_binary_long(data); - unsigned long period_count = read_binary_long(data); - unsigned long xact_count = read_binary_number(data); - unsigned long bigint_count = read_binary_number(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(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 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(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(data); - commodities = commodities_next = new commodity_t *[c_count]; - - for (commodity_t::ident_t i = 0; i < c_count; i++) { - commodity_t * commodity; - 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 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(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; - entry_pool->journal = journal; - read_binary_entry(data, entry_pool, xact_pool, finalize); - 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; -} -#endif - -#if 0 -bool binary_parser_t::test(std::istream& in) const -{ - if (read_binary_number_nocheck(in) == binary_magic_number && - read_binary_number_nocheck(in) == format_version) - return true; - - in.clear(); - in.seekg(0, std::ios::beg); - return false; -} - -unsigned int binary_parser_t::parse(std::istream& in, - journal_t * journal, - account_t * master, - const string * original_file) -{ -#if 0 - return read_binary_journal(in, journal, master, - original_file ? *original_file : ""); -#endif -} -#endif - - -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); -} - -void write_binary_string(std::ostream& out, const string& str) -{ - write_binary_guard(out, 0x3001); - - unsigned long len = str.length(); - if (len > 255) { - assert(len < 65536); - write_binary_number_nocheck(out, 0xff); - write_binary_number_nocheck(out, len); - } else { - write_binary_number_nocheck(out, len); - } - - if (len) - out.write(str.c_str(), len); - - write_binary_guard(out, 0x3002); -} - -#if 0 -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, *((moment_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_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(out, 0); - write_binary_amount(out, amount_t()); - } - else if (! xact->amount_expr.empty()) { - write_binary_number(out, 1); - write_binary_string(out, xact->amount_expr); - } - else { - write_binary_number(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.empty()) { - 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_string(out, entry->predicate.expr); -} - -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(out, 0); - } else { - write_binary_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(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(out, 0xffffffff); - - write_binary_string(out, account->name); - write_binary_string(out, account->note); - write_binary_number(out, account->depth); - - write_binary_long(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(out, 0); - } else { - write_binary_number(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(out, 0); - - // Write out the accounts - - write_binary_long(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(out, journal->entries.size()); - write_binary_long(out, journal->auto_entries.size()); - write_binary_long(out, journal->period_entries.size()); - - ostream_pos_type xacts_val = out.tellp(); - write_binary_number(out, 0); - - ostream_pos_type bigints_val = out.tellp(); - write_binary_number(out, 0); - - bigints_count = 0; - - // Write out the commodities - - write_binary_long - (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 - (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(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(out, data_size); - out.seekp(xacts_val); - write_binary_number(out, xact_count); - out.seekp(bigints_val); - write_binary_number(out, bigints_count); -} -#endif - -} // namespace ledger diff --git a/binary.h b/binary.h deleted file mode 100644 index 528217fa..00000000 --- a/binary.h +++ /dev/null @@ -1,252 +0,0 @@ -#ifndef _BINARY_H -#define _BINARY_H - -#include "parser.h" - -namespace ledger { - -#if 0 -class binary_parser_t : public parser_t -{ - public: - virtual bool test(std::istream& in) const; - - virtual unsigned int parse(std::istream& in, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); -}; -#endif - -template -inline void read_binary_number_nocheck(std::istream& in, T& num) { - in.read((char *)&num, sizeof(num)); -} - -template -inline void read_binary_number_nocheck(char *& data, T& num) { - num = *((T *) data); - data += sizeof(T); -} - -template -inline T read_binary_number_nocheck(std::istream& in) { - T num; - read_binary_number_nocheck(in, num); - return num; -} - -template -inline T read_binary_number_nocheck(char *& data) { - T num; - read_binary_number_nocheck(data, num); - return num; -} - -#if DEBUG_LEVEL >= ALPHA -#define read_binary_guard(in, id) \ - if (read_binary_number_nocheck(in) != id) \ - assert(0); -#else -#define read_binary_guard(in, id) -#endif - -template -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); -} - -template -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); -} - -template -inline T read_binary_number(std::istream& in) { - T num; - read_binary_number(in, num); - return num; -} - -template -inline T read_binary_number(char *& data) { - T num; - read_binary_number(data, num); - return num; -} - -void read_binary_bool(std::istream& in, bool& num); -void read_binary_bool(char *& data, bool& num); - -inline bool read_binary_bool(std::istream& in) { - bool num; - read_binary_bool(in, num); - return num; -} - -inline bool read_binary_bool(char *& data) { - bool num; - read_binary_bool(data, num); - return num; -} - -template -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 -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 -inline T read_binary_long(std::istream& in) { - T num; - read_binary_long(in, num); - return num; -} - -template -inline T read_binary_long(char *& data) { - T num; - read_binary_long(data, num); - return num; -} - -void read_binary_string(std::istream& in, string& str); -void read_binary_string(char *& data, string& str); -void read_binary_string(char *& data, string * str); - -inline string read_binary_string(std::istream& in) { - string temp; - read_binary_string(in, temp); - return temp; -} - -inline string read_binary_string(char *& data) { - string temp; - read_binary_string(data, temp); - return temp; -} - - -template -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(out, id) -#else -#define write_binary_guard(in, id) -#endif - -template -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); -} - -void write_binary_bool(std::ostream& out, bool num); - -template -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(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); -} - -void write_binary_string(std::ostream& out, const string& str); - - - -#if 0 -void write_binary_journal(std::ostream& out, journal_t * journal); -#endif - -} // namespace ledger - -#endif // _BINARY_H diff --git a/compile b/compile deleted file mode 100755 index 1b1d2321..00000000 --- a/compile +++ /dev/null @@ -1,142 +0,0 @@ -#! /bin/sh -# Wrapper for compilers which do not understand `-c -o'. - -scriptversion=2005-05-14.22 - -# Copyright (C) 1999, 2000, 2003, 2004, 2005 Free Software Foundation, Inc. -# Written by Tom Tromey . -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# This file is maintained in Automake, please report -# bugs to or send patches to -# . - -case $1 in - '') - echo "$0: No command. Try \`$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: compile [--help] [--version] PROGRAM [ARGS] - -Wrapper for compilers which do not understand `-c -o'. -Remove `-o dest.o' from ARGS, run PROGRAM with the remaining -arguments, and rename the output as expected. - -If you are trying to build a whole package this is not the -right script to run: please start by reading the file `INSTALL'. - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "compile $scriptversion" - exit $? - ;; -esac - -ofile= -cfile= -eat= - -for arg -do - if test -n "$eat"; then - eat= - else - case $1 in - -o) - # configure might choose to run compile as `compile cc -o foo foo.c'. - # So we strip `-o arg' only if arg is an object. - eat=1 - case $2 in - *.o | *.obj) - ofile=$2 - ;; - *) - set x "$@" -o "$2" - shift - ;; - esac - ;; - *.c) - cfile=$1 - set x "$@" "$1" - shift - ;; - *) - set x "$@" "$1" - shift - ;; - esac - fi - shift -done - -if test -z "$ofile" || test -z "$cfile"; then - # If no `-o' option was seen then we might have been invoked from a - # pattern rule where we don't need one. That is ok -- this is a - # normal compilation that the losing compiler can handle. If no - # `.c' file was seen then we are probably linking. That is also - # ok. - exec "$@" -fi - -# Name of file we expect compiler to create. -cofile=`echo "$cfile" | sed -e 's|^.*/||' -e 's/\.c$/.o/'` - -# Create the lock directory. -# Note: use `[/.-]' here to ensure that we don't use the same name -# that we are using for the .o file. Also, base the name on the expected -# object file name, since that is what matters with a parallel build. -lockdir=`echo "$cofile" | sed -e 's|[/.-]|_|g'`.d -while true; do - if mkdir "$lockdir" >/dev/null 2>&1; then - break - fi - sleep 1 -done -# FIXME: race condition here if user kills between mkdir and trap. -trap "rmdir '$lockdir'; exit 1" 1 2 15 - -# Run the compile. -"$@" -ret=$? - -if test -f "$cofile"; then - mv "$cofile" "$ofile" -elif test -f "${cofile}bj"; then - mv "${cofile}bj" "$ofile" -fi - -rmdir "$lockdir" -exit $ret - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-end: "$" -# End: diff --git a/configure b/configure index def7f537..a62cf064 100755 --- a/configure +++ b/configure @@ -733,7 +733,7 @@ PACKAGE_STRING='ledger 3.0-git' PACKAGE_BUGREPORT='johnw@newartisans.com' ac_unique_file="ledger" -ac_unique_file="main.cc" +ac_unique_file="src/main.cc" # Factoring default headers for most tests. ac_includes_default="\ #include diff --git a/configure.in b/configure.in index c65ec096..8259d1fe 100644 --- a/configure.in +++ b/configure.in @@ -7,7 +7,7 @@ AC_INIT(ledger, 3.0-git, johnw@newartisans.com) AC_CONFIG_SRCDIR(ledger) AM_INIT_AUTOMAKE -AC_CONFIG_SRCDIR([main.cc]) +AC_CONFIG_SRCDIR([src/main.cc]) AC_CONFIG_HEADER([acconf.h]) AC_CONFIG_SUBDIRS([gdtoa]) diff --git a/context.h b/context.h deleted file mode 100644 index 3851d073..00000000 --- a/context.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef _CONTEXT_H -#define _CONTEXT_H - -namespace ledger { - -class context -{ -public: - string context; // ex: 'While parsing file "%R" at line %L' - - string resource; // ex: ledger.dat - long linenum_beg; // ex: 1010 - long linenum_end; // ex: 1010 - long colnum_beg; // ex: 8 - long colnum_end; // ex: 8 - long position_beg; - long position_end; - - string text; // ex: (The multi-line text of an entry) - long linenum_beg_off; // ex: 2 / -1 means start at beginning - long linenum_end_off; // ex: 2 / -1 means start at beginning - long colnum_beg_off; // ex: 8 / -1 means start - long colnum_end_off; // ex: 8 / -1 means start -}; - -} // namespace ledger - -#endif // _CONTEXT_H diff --git a/contrib/ledger.vim b/contrib/ledger.vim new file mode 100644 index 00000000..df63feb8 --- /dev/null +++ b/contrib/ledger.vim @@ -0,0 +1,46 @@ +" Vim syntax file +" filetype: ledger +" Version: 0.0.2 +" by Wolfgang Oertl; Use according to the terms of the GPL>=2. +" Revision history +" 2005-02-05 first version (partly copied from ledger.vim 0.0.1) + +if version < 600 + syntax clear +elseif exists("b:current_sytax") + finish +endif + +" for debugging +syntax clear + +" region: a normal transaction +syn region transNorm start=/^\d/ skip=/^\s/ end=/^/ fold keepend transparent contains=transDate +syn match transDate /^\d\S\+/ contained +syn match Comment /^;.*$/ +" highlight default link transNorm Question +highlight default link Comment SpecialKey +highlight default link transDate Question + +" folding: how to represent a transaction in one line. +function! MyFoldText() + let line = strpart(getline(v:foldstart), 0, 65) + " get the amount at the end of the second line + let line2 = getline(v:foldstart+1) + let pos = match(line2, "[0-9.]*$") + let line2 = strpart(line2, pos) + let pad_len = 80 - strlen(line) - strlen(line2) + if (pad_len < 0) then + pad_len = 0 + endif + let pad = strpart(" ", 0, pad_len) + return line . pad . line2 +endfunction +set foldtext=MyFoldText() +set foldmethod=syntax + +" syncinc is easy: search for the first transaction. +syn sync clear +syn sync match ledgerSync grouphere transNorm "^\d" + +let b:current_syntax = "ledger" diff --git a/contrib/ledger.xcodeproj/johnw.mode1 b/contrib/ledger.xcodeproj/johnw.mode1 new file mode 100644 index 00000000..0c179deb --- /dev/null +++ b/contrib/ledger.xcodeproj/johnw.mode1 @@ -0,0 +1,1434 @@ + + + + + ActivePerspectiveName + Project + AllowedModules + + + BundleLoadPath + + MaxInstances + n + Module + PBXSmartGroupTreeModule + Name + Groups and Files Outline View + + + BundleLoadPath + + MaxInstances + n + Module + PBXNavigatorGroup + Name + Editor + + + BundleLoadPath + + MaxInstances + n + Module + XCTaskListModule + Name + Task List + + + BundleLoadPath + + MaxInstances + n + Module + XCDetailModule + Name + File and Smart Group Detail Viewer + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXBuildResultsModule + Name + Detailed Build Results Viewer + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXProjectFindModule + Name + Project Batch Find Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXRunSessionModule + Name + Run Log + + + BundleLoadPath + + MaxInstances + n + Module + PBXBookmarksModule + Name + Bookmarks Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXClassBrowserModule + Name + Class Browser + + + BundleLoadPath + + MaxInstances + n + Module + PBXCVSModule + Name + Source Code Control Tool + + + BundleLoadPath + + MaxInstances + n + Module + PBXDebugBreakpointsModule + Name + Debug Breakpoints Tool + + + BundleLoadPath + + MaxInstances + n + Module + XCDockableInspector + Name + Inspector + + + BundleLoadPath + + MaxInstances + n + Module + PBXOpenQuicklyModule + Name + Open Quickly Tool + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXDebugSessionModule + Name + Debugger + + + BundleLoadPath + + MaxInstances + 1 + Module + PBXDebugCLIModule + Name + Debug Console + + + Description + DefaultDescriptionKey + DockingSystemVisible + + Extension + mode1 + FavBarConfig + + PBXProjectModuleGUID + 33AD83720B8027C500CF4200 + XCBarModuleItemNames + + XCBarModuleItems + + + FirstTimeWindowDisplayed + + Identifier + com.apple.perspectives.project.mode1 + MajorVersion + 31 + MinorVersion + 1 + Name + Default + Notifications + + + XCObserverAutoDisconnectKey + + XCObserverDefintionKey + + PBXStatusErrorsKey + 0 + + XCObserverFactoryKey + XCPerspectivesSpecificationIdentifier + XCObserverGUIDKey + XCObserverProjectIdentifier + XCObserverNotificationKey + PBXStatusBuildStateMessageNotification + XCObserverTargetKey + XCMainBuildResultsModuleGUID + XCObserverTriggerKey + awakenModuleWithObserver: + XCObserverValidationKey + + PBXStatusErrorsKey + 2 + + + + XCObserverAutoDisconnectKey + + XCObserverDefintionKey + + PBXStatusWarningsKey + 0 + + XCObserverFactoryKey + XCPerspectivesSpecificationIdentifier + XCObserverGUIDKey + XCObserverProjectIdentifier + XCObserverNotificationKey + PBXStatusBuildStateMessageNotification + XCObserverTargetKey + XCMainBuildResultsModuleGUID + XCObserverTriggerKey + awakenModuleWithObserver: + XCObserverValidationKey + + PBXStatusWarningsKey + 2 + + + + OpenEditors + + PerspectiveWidths + + -1 + -1 + + Perspectives + + + ChosenToolbarItems + + active-target-popup + action + NSToolbarFlexibleSpaceItem + buildOrClean + build-and-runOrDebug + com.apple.ide.PBXToolbarStopButton + get-info + toggle-editor + NSToolbarFlexibleSpaceItem + com.apple.pbx.toolbar.searchfield + + ControllerClassBaseName + + IconName + WindowOfProjectWithEditor + Identifier + perspective.project + IsVertical + + Layout + + + BecomeActive + + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C08E77C0454961000C914BD + 1C37FABC05509CD000000102 + 1C37FABC05539CD112110102 + E2644B35053B69B200211256 + 1C37FABC04509CD000100104 + 1CC0EA4004350EF90044410B + 1CC0EA4004350EF90041110B + + PBXProjectModuleGUID + 1CE0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + yes + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 186 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 08FB7794FE84155DC02AAC07 + 08FB7795FE84155DC02AAC07 + 3332304B0B802B5500C403F5 + 333230630B802BB200C403F5 + 3332304F0B802B6500C403F5 + 333230590B802B8E00C403F5 + C6859E8C029090F304C91782 + 33B8460F0BD0A60100472F4E + 1AB674ADFE9D54B511CA2CBB + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C37FABC05509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {186, 764}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + + XCSharingToken + com.apple.Xcode.GFSharingToken + + GeometryConfiguration + + Frame + {{0, 0}, {203, 782}} + GroupTreeTableConfiguration + + MainColumn + 186 + + RubberWindowFrame + 206 55 1041 823 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 203pt + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CE0B20306471E060097A5F4 + PBXProjectModuleLabel + amount.cc + PBXSplitModuleInNavigatorKey + + Split0 + + PBXProjectModuleGUID + 1CE0B20406471E060097A5F4 + PBXProjectModuleLabel + amount.cc + _historyCapacity + 0 + bookmark + 3357D0BB0BD4A651004B3223 + history + + 333230340B802B2C00C403F5 + 333230760B802C3300C403F5 + 3332307B0B802C4100C403F5 + 3332308E0B802C7A00C403F5 + 333230960B802C9A00C403F5 + 333230A30B802D4000C403F5 + 333230A40B802D4000C403F5 + 333230A70B802D4000C403F5 + 333230A80B802D4000C403F5 + 333230A90B802D4000C403F5 + 333230AA0B802D4000C403F5 + 333230AB0B802D4000C403F5 + 333230AC0B802D4000C403F5 + 333230AD0B802D4000C403F5 + 333230AF0B802D4000C403F5 + 333231000B802FF000C403F5 + 33B8460B0BD0A5CC00472F4E + 33B846130BD0A63200472F4E + 33B846400BD0A6EB00472F4E + 3357D0B80BD4A651004B3223 + 3357D0B90BD4A651004B3223 + + prevStack + + 333230360B802B2C00C403F5 + 333230700B802C1B00C403F5 + 333230740B802C2700C403F5 + 333230780B802C3300C403F5 + 3332307D0B802C4100C403F5 + 3332307E0B802C4100C403F5 + 333230820B802C4D00C403F5 + 333230860B802C6100C403F5 + 3332308B0B802C7100C403F5 + 3332308C0B802C7100C403F5 + 333230900B802C7A00C403F5 + 333230940B802C8B00C403F5 + 333230990B802C9A00C403F5 + 3332309A0B802C9A00C403F5 + 333230B20B802D4000C403F5 + 333230B40B802D4000C403F5 + 333230BA0B802D4000C403F5 + 333230BE0B802D4000C403F5 + 333230C00B802D4000C403F5 + 333230C20B802D4000C403F5 + 33B8460D0BD0A5CC00472F4E + 3357D0BA0BD4A651004B3223 + + + SplitCount + 1 + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {833, 544}} + RubberWindowFrame + 206 55 1041 823 0 0 1440 878 + + Module + PBXNavigatorGroup + Proportion + 544pt + + + ContentConfiguration + + PBXProjectModuleGUID + 1CE0B20506471E060097A5F4 + PBXProjectModuleLabel + Detail + + GeometryConfiguration + + Frame + {{0, 549}, {833, 233}} + RubberWindowFrame + 206 55 1041 823 0 0 1440 878 + + Module + XCDetailModule + Proportion + 233pt + + + Proportion + 833pt + + + Name + Project + ServiceClasses + + XCModuleDock + PBXSmartGroupTreeModule + XCModuleDock + PBXNavigatorGroup + XCDetailModule + + TableOfContents + + 3357D0950BD4A3DB004B3223 + 1CE0B1FE06471DED0097A5F4 + 3357D0960BD4A3DB004B3223 + 1CE0B20306471E060097A5F4 + 1CE0B20506471E060097A5F4 + + ToolbarConfiguration + xcode.toolbar.config.default + + + ControllerClassBaseName + + IconName + WindowOfProject + Identifier + perspective.morph + IsVertical + 0 + Layout + + + BecomeActive + 1 + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C37FBAC04509CD000000102 + 1C37FAAC04509CD000000102 + 1C08E77C0454961000C914BD + 1C37FABC05509CD000000102 + 1C37FABC05539CD112110102 + E2644B35053B69B200211256 + 1C37FABC04509CD000100104 + 1CC0EA4004350EF90044410B + 1CC0EA4004350EF90041110B + + PBXProjectModuleGUID + 11E0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + yes + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 186 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 29B97314FDCFA39411CA2CEA + 1C37FABC05509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {186, 337}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + 1 + XCSharingToken + com.apple.Xcode.GFSharingToken + + GeometryConfiguration + + Frame + {{0, 0}, {203, 355}} + GroupTreeTableConfiguration + + MainColumn + 186 + + RubberWindowFrame + 373 269 690 397 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 100% + + + Name + Morph + PreferredWidth + 300 + ServiceClasses + + XCModuleDock + PBXSmartGroupTreeModule + + TableOfContents + + 11E0B1FE06471DED0097A5F4 + + ToolbarConfiguration + xcode.toolbar.config.default.short + + + PerspectivesBarVisible + + ShelfIsVisible + + SourceDescription + file at '/System/Library/PrivateFrameworks/DevToolsInterface.framework/Versions/A/Resources/XCPerspectivesSpecificationMode1.xcperspec' + StatusbarIsVisible + + TimeStamp + 198485585.74654299 + ToolbarDisplayMode + 1 + ToolbarIsVisible + + ToolbarSizeMode + 1 + Type + Perspectives + UpdateMessage + The Default Workspace in this version of Xcode now includes support to hide and show the detail view (what has been referred to as the "Metro-Morph" feature). You must discard your current Default Workspace settings and update to the latest Default Workspace in order to gain this feature. Do you wish to update to the latest Workspace defaults for project '%@'? + WindowJustification + 5 + WindowOrderList + + /Volumes/Users/johnw/Projects/local/ledger/trunk/ledger.xcodeproj + 33AD83730B8027C500CF4200 + + WindowString + 206 55 1041 823 0 0 1440 878 + WindowTools + + + FirstTimeWindowDisplayed + + Identifier + windowTool.build + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CD0528F0623707200166675 + PBXProjectModuleLabel + amount.cc + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {730, 268}} + RubberWindowFrame + 397 238 730 550 0 0 1440 878 + + Module + PBXNavigatorGroup + Proportion + 268pt + + + BecomeActive + + ContentConfiguration + + PBXBuildLogShowsTranscriptDefaultKey + {{0, 93}, {730, 143}} + PBXProjectModuleGUID + XCMainBuildResultsModuleGUID + PBXProjectModuleLabel + Build + XCBuildResultsTrigger_Collapse + 1022 + XCBuildResultsTrigger_Open + 1013 + + GeometryConfiguration + + Frame + {{0, 273}, {730, 236}} + RubberWindowFrame + 397 238 730 550 0 0 1440 878 + + Module + PBXBuildResultsModule + Proportion + 236pt + + + Proportion + 509pt + + + Name + Build Results + ServiceClasses + + PBXBuildResultsModule + + StatusbarIsVisible + + TableOfContents + + 33AD83730B8027C500CF4200 + 3357D0990BD4A3E5004B3223 + 1CD0528F0623707200166675 + XCMainBuildResultsModuleGUID + + ToolbarConfiguration + xcode.toolbar.config.build + WindowString + 397 238 730 550 0 0 1440 878 + WindowToolGUID + 33AD83730B8027C500CF4200 + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.debugger + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + Debugger + + HorizontalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {377, 331}} + {{377, 0}, {489, 331}} + + + VerticalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {866, 331}} + {{0, 331}, {866, 416}} + + + + LauncherConfigVersion + 8 + PBXProjectModuleGUID + 1C162984064C10D400B95A72 + PBXProjectModuleLabel + Debug - GLUTExamples (Underwater) + + GeometryConfiguration + + DebugConsoleDrawerSize + {100, 120} + DebugConsoleVisible + None + DebugConsoleWindowFrame + {{200, 200}, {500, 300}} + DebugSTDIOWindowFrame + {{200, 200}, {500, 300}} + Frame + {{0, 0}, {866, 747}} + RubberWindowFrame + 106 71 866 788 0 0 1440 878 + + Module + PBXDebugSessionModule + Proportion + 747pt + + + Proportion + 747pt + + + Name + Debugger + ServiceClasses + + PBXDebugSessionModule + + StatusbarIsVisible + + TableOfContents + + 1CD10A99069EF8BA00B06720 + 3332303A0B802B2C00C403F5 + 1C162984064C10D400B95A72 + 3332303B0B802B2C00C403F5 + 3332303C0B802B2C00C403F5 + 3332303D0B802B2C00C403F5 + 3332303E0B802B2C00C403F5 + 3332303F0B802B2C00C403F5 + 333230400B802B2C00C403F5 + + ToolbarConfiguration + xcode.toolbar.config.debug + WindowString + 106 71 866 788 0 0 1440 878 + WindowToolGUID + 1CD10A99069EF8BA00B06720 + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.find + IsVertical + + Layout + + + Dock + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1CDD528C0622207200134675 + PBXProjectModuleLabel + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {781, 212}} + RubberWindowFrame + 227 385 781 470 0 0 1440 878 + + Module + PBXNavigatorGroup + Proportion + 781pt + + + Proportion + 212pt + + + ContentConfiguration + + PBXProjectModuleGUID + 1CD0528E0623707200166675 + PBXProjectModuleLabel + Project Find + + GeometryConfiguration + + Frame + {{0, 217}, {781, 212}} + RubberWindowFrame + 227 385 781 470 0 0 1440 878 + + Module + PBXProjectFindModule + Proportion + 212pt + + + Proportion + 429pt + + + Name + Project Find + ServiceClasses + + PBXProjectFindModule + + StatusbarIsVisible + + TableOfContents + + 1C530D57069F1CE1000CFCEE + 3332309E0B802CA500C403F5 + 3332309F0B802CA500C403F5 + 1CDD528C0622207200134675 + 1CD0528E0623707200166675 + + WindowString + 227 385 781 470 0 0 1440 878 + WindowToolGUID + 1C530D57069F1CE1000CFCEE + WindowToolIsVisible + + + + Identifier + MENUSEPARATOR + + + FirstTimeWindowDisplayed + + Identifier + windowTool.debuggerConsole + IsVertical + + Layout + + + Dock + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1C78EAAC065D492600B07095 + PBXProjectModuleLabel + Debugger Console + + GeometryConfiguration + + Frame + {{0, 0}, {440, 358}} + RubberWindowFrame + 127 436 440 400 0 0 1440 878 + + Module + PBXDebugCLIModule + Proportion + 358pt + + + Proportion + 359pt + + + Name + Debugger Console + ServiceClasses + + PBXDebugCLIModule + + StatusbarIsVisible + + TableOfContents + + 333230D00B802DB800C403F5 + 333230D10B802DB800C403F5 + 1C78EAAC065D492600B07095 + + WindowString + 127 436 440 400 0 0 1440 878 + WindowToolGUID + 333230D00B802DB800C403F5 + WindowToolIsVisible + + + + Identifier + windowTool.run + Layout + + + Dock + + + ContentConfiguration + + LauncherConfigVersion + 3 + PBXProjectModuleGUID + 1CD0528B0623707200166675 + PBXProjectModuleLabel + Run + Runner + + HorizontalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {493, 167}} + {{0, 176}, {493, 267}} + + + VerticalSplitView + + _collapsingFrameDimension + 0.0 + _indexOfCollapsedView + 0 + _percentageOfCollapsedView + 0.0 + isCollapsed + yes + sizes + + {{0, 0}, {405, 443}} + {{414, 0}, {514, 443}} + + + + + GeometryConfiguration + + Frame + {{0, 0}, {460, 159}} + RubberWindowFrame + 316 696 459 200 0 0 1280 1002 + + Module + PBXRunSessionModule + Proportion + 159pt + + + Proportion + 159pt + + + Name + Run Log + ServiceClasses + + PBXRunSessionModule + + StatusbarIsVisible + 1 + TableOfContents + + 1C0AD2B3069F1EA900FABCE6 + 1C0AD2B4069F1EA900FABCE6 + 1CD0528B0623707200166675 + 1C0AD2B5069F1EA900FABCE6 + + ToolbarConfiguration + xcode.toolbar.config.run + WindowString + 316 696 459 200 0 0 1280 1002 + WindowToolGUID + 1C0AD2B3069F1EA900FABCE6 + WindowToolIsVisible + 0 + + + FirstTimeWindowDisplayed + + Identifier + windowTool.scm + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + PBXProjectModuleGUID + 1C78EAB2065D492600B07095 + PBXProjectModuleLabel + + StatusBarVisibility + + + GeometryConfiguration + + Frame + {{0, 0}, {452, 0}} + RubberWindowFrame + 227 547 452 308 0 0 1440 878 + + Module + PBXNavigatorGroup + Proportion + 0pt + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1CD052920623707200166675 + PBXProjectModuleLabel + SCM Results + + GeometryConfiguration + + Frame + {{0, 5}, {452, 262}} + RubberWindowFrame + 227 547 452 308 0 0 1440 878 + + Module + PBXCVSModule + Proportion + 262pt + + + Proportion + 267pt + + + Name + SCM + ServiceClasses + + PBXCVSModule + + StatusbarIsVisible + + TableOfContents + + 333230D30B802DD900C403F5 + 333230D40B802DD900C403F5 + 1C78EAB2065D492600B07095 + 1CD052920623707200166675 + + ToolbarConfiguration + xcode.toolbar.config.scm + WindowString + 227 547 452 308 0 0 1440 878 + WindowToolGUID + 333230D30B802DD900C403F5 + WindowToolIsVisible + + + + FirstTimeWindowDisplayed + + Identifier + windowTool.breakpoints + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + PBXBottomSmartGroupGIDs + + 1C77FABC04509CD000000102 + + PBXProjectModuleGUID + 1CE0B1FE06471DED0097A5F4 + PBXProjectModuleLabel + Files + PBXProjectStructureProvided + no + PBXSmartGroupTreeModuleColumnData + + PBXSmartGroupTreeModuleColumnWidthsKey + + 168 + + PBXSmartGroupTreeModuleColumnsKey_v4 + + MainColumn + + + PBXSmartGroupTreeModuleOutlineStateKey_v7 + + PBXSmartGroupTreeModuleOutlineStateExpansionKey + + 1C77FABC04509CD000000102 + + PBXSmartGroupTreeModuleOutlineStateSelectionKey + + + 0 + + + PBXSmartGroupTreeModuleOutlineStateVisibleRectKey + {{0, 0}, {168, 350}} + + PBXTopSmartGroupGIDs + + XCIncludePerspectivesSwitch + + + GeometryConfiguration + + Frame + {{0, 0}, {185, 368}} + GroupTreeTableConfiguration + + MainColumn + 168 + + RubberWindowFrame + 127 427 744 409 0 0 1440 878 + + Module + PBXSmartGroupTreeModule + Proportion + 185pt + + + BecomeActive + + ContentConfiguration + + PBXProjectModuleGUID + 1CA1AED706398EBD00589147 + PBXProjectModuleLabel + Detail + + GeometryConfiguration + + Frame + {{190, 0}, {554, 368}} + RubberWindowFrame + 127 427 744 409 0 0 1440 878 + + Module + XCDetailModule + Proportion + 554pt + + + Proportion + 368pt + + + MajorVersion + 2 + MinorVersion + 0 + Name + Breakpoints + ServiceClasses + + PBXSmartGroupTreeModule + XCDetailModule + + StatusbarIsVisible + + TableOfContents + + 333230F90B802FDD00C403F5 + 333230FA0B802FDD00C403F5 + 1CE0B1FE06471DED0097A5F4 + 1CA1AED706398EBD00589147 + + ToolbarConfiguration + xcode.toolbar.config.breakpoints + WindowString + 127 427 744 409 0 0 1440 878 + WindowToolGUID + 333230F90B802FDD00C403F5 + WindowToolIsVisible + + + + Identifier + windowTool.debugAnimator + Layout + + + Dock + + + Module + PBXNavigatorGroup + Proportion + 100% + + + Proportion + 100% + + + Name + Debug Visualizer + ServiceClasses + + PBXNavigatorGroup + + StatusbarIsVisible + 1 + ToolbarConfiguration + xcode.toolbar.config.debugAnimator + WindowString + 100 100 700 500 0 0 1280 1002 + + + Identifier + windowTool.bookmarks + Layout + + + Dock + + + Module + PBXBookmarksModule + Proportion + 100% + + + Proportion + 100% + + + Name + Bookmarks + ServiceClasses + + PBXBookmarksModule + + StatusbarIsVisible + 0 + WindowString + 538 42 401 187 0 0 1280 1002 + + + FirstTimeWindowDisplayed + + Identifier + windowTool.classBrowser + IsVertical + + Layout + + + Dock + + + ContentConfiguration + + OptionsSetName + Hierarchy, project classes + PBXProjectModuleGUID + 1CA6456E063B45B4001379D8 + PBXProjectModuleLabel + Class Browser - value_t + + GeometryConfiguration + + ClassesFrame + {{0, 0}, {378, 96}} + ClassesTreeTableConfiguration + + PBXClassNameColumnIdentifier + 208 + PBXClassBookColumnIdentifier + 22 + + Frame + {{0, 0}, {630, 332}} + MembersFrame + {{0, 101}, {378, 231}} + MembersTreeTableConfiguration + + PBXMemberTypeIconColumnIdentifier + 22 + PBXMemberNameColumnIdentifier + 216 + PBXMemberTypeColumnIdentifier + 101 + PBXMemberBookColumnIdentifier + 22 + + RubberWindowFrame + 227 503 630 352 0 0 1440 878 + + Module + PBXClassBrowserModule + Proportion + 332pt + + + Proportion + 332pt + + + Name + Class Browser + ServiceClasses + + PBXClassBrowserModule + + StatusbarIsVisible + + TableOfContents + + 1C0AD2AF069F1E9B00FABCE6 + 333230E20B802E8300C403F5 + 1CA6456E063B45B4001379D8 + + ToolbarConfiguration + xcode.toolbar.config.classbrowser + WindowString + 227 503 630 352 0 0 1440 878 + WindowToolGUID + 1C0AD2AF069F1E9B00FABCE6 + WindowToolIsVisible + + + + + diff --git a/contrib/ledger.xcodeproj/johnw.pbxuser b/contrib/ledger.xcodeproj/johnw.pbxuser new file mode 100644 index 00000000..d3c3754b --- /dev/null +++ b/contrib/ledger.xcodeproj/johnw.pbxuser @@ -0,0 +1,861 @@ +// !$*UTF8*$! +{ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + activeBuildConfigurationName = Debug; + activeExecutable = 33AD82D60B80262200CF4200 /* ledger */; + activeTarget = 8DD76F620486A84900D96B5E /* ledger */; + addToTargets = ( + 8DD76F620486A84900D96B5E /* ledger */, + ); + breakpoints = ( + 333230A20B802D3E00C403F5 /* xpath.h:768 */, + ); + breakpointsGroup = 333231030B802FF000C403F5 /* XCBreakpointsBucket */; + codeSenseManager = 33AD82DB0B80264000CF4200 /* Code sense */; + executables = ( + 33AD82D60B80262200CF4200 /* ledger */, + ); + perUserDictionary = { + "PBXConfiguration.PBXBreakpointsDataSource.v1:1CA1AED706398EBD00589147" = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXBreakpointsDataSource_BreakpointID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 20, + 210, + 20, + 110, + 109, + 20, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXBreakpointsDataSource_ActionID, + PBXBreakpointsDataSource_TypeID, + PBXBreakpointsDataSource_BreakpointID, + PBXBreakpointsDataSource_UseID, + PBXBreakpointsDataSource_LocationID, + PBXBreakpointsDataSource_ConditionID, + PBXBreakpointsDataSource_ContinueID, + ); + }; + PBXConfiguration.PBXFileTableDataSource3.PBXExecutablesDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXExecutablesDataSource_NameID; + PBXFileTableDataSourceColumnWidthsKey = ( + 22, + 300, + 481.5835, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXExecutablesDataSource_ActiveFlagID, + PBXExecutablesDataSource_NameID, + PBXExecutablesDataSource_CommentsID, + ); + }; + PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 594, + 20, + 48, + 43, + 43, + 20, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXFileDataSource_FiletypeID, + PBXFileDataSource_Filename_ColumnID, + PBXFileDataSource_Built_ColumnID, + PBXFileDataSource_ObjectSize_ColumnID, + PBXFileDataSource_Errors_ColumnID, + PBXFileDataSource_Warnings_ColumnID, + PBXFileDataSource_Target_ColumnID, + ); + }; + PBXConfiguration.PBXTargetDataSource.PBXTargetDataSource = { + PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; + PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; + PBXFileTableDataSourceColumnWidthsKey = ( + 20, + 200, + 63, + 20, + 48, + 43, + 43, + ); + PBXFileTableDataSourceColumnsKey = ( + PBXFileDataSource_FiletypeID, + PBXFileDataSource_Filename_ColumnID, + PBXTargetDataSource_PrimaryAttribute, + PBXFileDataSource_Built_ColumnID, + PBXFileDataSource_ObjectSize_ColumnID, + PBXFileDataSource_Errors_ColumnID, + PBXFileDataSource_Warnings_ColumnID, + ); + }; + PBXPerProjectTemplateStateSaveDate = 198484953; + PBXWorkspaceStateSaveDate = 198484953; + }; + perUserProjectItems = { + 333230340B802B2C00C403F5 /* PBXTextBookmark */ = 333230340B802B2C00C403F5 /* PBXTextBookmark */; + 333230360B802B2C00C403F5 /* PBXTextBookmark */ = 333230360B802B2C00C403F5 /* PBXTextBookmark */; + 333230700B802C1B00C403F5 /* PBXTextBookmark */ = 333230700B802C1B00C403F5 /* PBXTextBookmark */; + 333230740B802C2700C403F5 /* PBXTextBookmark */ = 333230740B802C2700C403F5 /* PBXTextBookmark */; + 333230760B802C3300C403F5 /* PBXTextBookmark */ = 333230760B802C3300C403F5 /* PBXTextBookmark */; + 333230780B802C3300C403F5 /* PBXTextBookmark */ = 333230780B802C3300C403F5 /* PBXTextBookmark */; + 3332307B0B802C4100C403F5 /* PBXTextBookmark */ = 3332307B0B802C4100C403F5 /* PBXTextBookmark */; + 3332307D0B802C4100C403F5 /* PBXTextBookmark */ = 3332307D0B802C4100C403F5 /* PBXTextBookmark */; + 3332307E0B802C4100C403F5 /* PBXTextBookmark */ = 3332307E0B802C4100C403F5 /* PBXTextBookmark */; + 333230820B802C4D00C403F5 /* PBXTextBookmark */ = 333230820B802C4D00C403F5 /* PBXTextBookmark */; + 333230860B802C6100C403F5 /* PBXTextBookmark */ = 333230860B802C6100C403F5 /* PBXTextBookmark */; + 3332308B0B802C7100C403F5 /* PBXTextBookmark */ = 3332308B0B802C7100C403F5 /* PBXTextBookmark */; + 3332308C0B802C7100C403F5 /* PBXTextBookmark */ = 3332308C0B802C7100C403F5 /* PBXTextBookmark */; + 3332308E0B802C7A00C403F5 /* PBXTextBookmark */ = 3332308E0B802C7A00C403F5 /* PBXTextBookmark */; + 333230900B802C7A00C403F5 /* PBXTextBookmark */ = 333230900B802C7A00C403F5 /* PBXTextBookmark */; + 333230940B802C8B00C403F5 /* PBXTextBookmark */ = 333230940B802C8B00C403F5 /* PBXTextBookmark */; + 333230960B802C9A00C403F5 /* PBXTextBookmark */ = 333230960B802C9A00C403F5 /* PBXTextBookmark */; + 333230990B802C9A00C403F5 /* PBXTextBookmark */ = 333230990B802C9A00C403F5 /* PBXTextBookmark */; + 3332309A0B802C9A00C403F5 /* PBXTextBookmark */ = 3332309A0B802C9A00C403F5 /* PBXTextBookmark */; + 333230A30B802D4000C403F5 /* PBXTextBookmark */ = 333230A30B802D4000C403F5 /* PBXTextBookmark */; + 333230A40B802D4000C403F5 /* PBXTextBookmark */ = 333230A40B802D4000C403F5 /* PBXTextBookmark */; + 333230A70B802D4000C403F5 /* PBXTextBookmark */ = 333230A70B802D4000C403F5 /* PBXTextBookmark */; + 333230A80B802D4000C403F5 /* PBXTextBookmark */ = 333230A80B802D4000C403F5 /* PBXTextBookmark */; + 333230A90B802D4000C403F5 /* PBXTextBookmark */ = 333230A90B802D4000C403F5 /* PBXTextBookmark */; + 333230AA0B802D4000C403F5 /* PBXTextBookmark */ = 333230AA0B802D4000C403F5 /* PBXTextBookmark */; + 333230AB0B802D4000C403F5 /* PBXTextBookmark */ = 333230AB0B802D4000C403F5 /* PBXTextBookmark */; + 333230AC0B802D4000C403F5 /* PBXTextBookmark */ = 333230AC0B802D4000C403F5 /* PBXTextBookmark */; + 333230AD0B802D4000C403F5 /* PBXTextBookmark */ = 333230AD0B802D4000C403F5 /* PBXTextBookmark */; + 333230AF0B802D4000C403F5 /* PBXTextBookmark */ = 333230AF0B802D4000C403F5 /* PBXTextBookmark */; + 333230B20B802D4000C403F5 /* PBXTextBookmark */ = 333230B20B802D4000C403F5 /* PBXTextBookmark */; + 333230B40B802D4000C403F5 /* PBXTextBookmark */ = 333230B40B802D4000C403F5 /* PBXTextBookmark */; + 333230BA0B802D4000C403F5 /* PBXTextBookmark */ = 333230BA0B802D4000C403F5 /* PBXTextBookmark */; + 333230BE0B802D4000C403F5 /* PBXTextBookmark */ = 333230BE0B802D4000C403F5 /* PBXTextBookmark */; + 333230C00B802D4000C403F5 /* PBXTextBookmark */ = 333230C00B802D4000C403F5 /* PBXTextBookmark */; + 333230C20B802D4000C403F5 /* PBXTextBookmark */ = 333230C20B802D4000C403F5 /* PBXTextBookmark */; + 333231000B802FF000C403F5 /* PBXTextBookmark */ = 333231000B802FF000C403F5 /* PBXTextBookmark */; + 3357D0B80BD4A651004B3223 /* PBXTextBookmark */ = 3357D0B80BD4A651004B3223 /* PBXTextBookmark */; + 3357D0B90BD4A651004B3223 /* PBXTextBookmark */ = 3357D0B90BD4A651004B3223 /* PBXTextBookmark */; + 3357D0BA0BD4A651004B3223 /* PBXTextBookmark */ = 3357D0BA0BD4A651004B3223 /* PBXTextBookmark */; + 3357D0BB0BD4A651004B3223 /* PBXTextBookmark */ = 3357D0BB0BD4A651004B3223 /* PBXTextBookmark */; + 33B8460B0BD0A5CC00472F4E /* PBXTextBookmark */ = 33B8460B0BD0A5CC00472F4E /* PBXTextBookmark */; + 33B8460D0BD0A5CC00472F4E /* PBXTextBookmark */ = 33B8460D0BD0A5CC00472F4E /* PBXTextBookmark */; + 33B846130BD0A63200472F4E /* PBXTextBookmark */ = 33B846130BD0A63200472F4E /* PBXTextBookmark */; + 33B846400BD0A6EB00472F4E /* PBXTextBookmark */ = 33B846400BD0A6EB00472F4E /* PBXTextBookmark */; + }; + sourceControlManager = 33AD82DA0B80264000CF4200 /* Source Control */; + userBuildSettings = { + }; + }; + 333230340B802B2C00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82F00B80269C00CF4200 /* format.cc */; + name = "format.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 808; + vrLoc = 0; + }; + 333230360B802B2C00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82F00B80269C00CF4200 /* format.cc */; + name = "format.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 808; + vrLoc = 0; + }; + 333230700B802C1B00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 3356EA090B8029FA00EC228D /* option.cc */; + name = "option.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 794; + vrLoc = 0; + }; + 333230740B802C2700C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83750B80280B00CF4200 /* acconf.h */; + name = "acconf.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1172; + vrLoc = 0; + }; + 333230760B802C3300C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83050B80269C00CF4200 /* quotes.cc */; + name = "quotes.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1072; + vrLoc = 0; + }; + 333230780B802C3300C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83050B80269C00CF4200 /* quotes.cc */; + name = "quotes.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1072; + vrLoc = 0; + }; + 3332307B0B802C4100C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82DD0B80269C00CF4200 /* amount.h */; + name = "TRACE_CTOR(\"amount_t()\");"; + rLen = 30; + rLoc = 920; + rType = 0; + vrLen = 645; + vrLoc = 0; + }; + 3332307D0B802C4100C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; + name = "static std::time_t base = -1;"; + rLen = 39; + rLoc = 595; + rType = 0; + vrLen = 740; + vrLoc = 0; + }; + 3332307E0B802C4100C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82DD0B80269C00CF4200 /* amount.h */; + name = "TRACE_CTOR(\"amount_t()\");"; + rLen = 30; + rLoc = 920; + rType = 0; + vrLen = 645; + vrLoc = 0; + }; + 333230820B802C4D00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; + name = "amount.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 801; + vrLoc = 0; + }; + 333230860B802C6100C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82F40B80269C00CF4200 /* journal.cc */; + name = "journal.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 723; + vrLoc = 0; + }; + 3332308B0B802C7100C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E00B80269C00CF4200 /* binary.cc */; + name = "binary.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1013; + vrLoc = 0; + }; + 3332308C0B802C7100C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83180B80269C00CF4200 /* xml.cc */; + name = "xml.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 912; + vrLoc = 0; + }; + 3332308E0B802C7A00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD831A0B80269C00CF4200 /* xmlparse.cc */; + name = "xmlparse.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 946; + vrLoc = 0; + }; + 333230900B802C7A00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD831A0B80269C00CF4200 /* xmlparse.cc */; + name = "xmlparse.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 946; + vrLoc = 0; + }; + 333230940B802C8B00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83070B80269C00CF4200 /* reconcile.cc */; + name = "reconcile.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 0; + vrLoc = 0; + }; + 333230960B802C9A00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83090B80269C00CF4200 /* report.cc */; + name = "report.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1023; + vrLoc = 0; + }; + 333230990B802C9A00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83090B80269C00CF4200 /* report.cc */; + name = "report.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1023; + vrLoc = 0; + }; + 3332309A0B802C9A00C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E20B80269C00CF4200 /* csv.cc */; + name = "csv.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 0; + vrLoc = 0; + }; + 333230A20B802D3E00C403F5 /* xpath.h:768 */ = { + isa = PBXFileBreakpoint; + actions = ( + ); + breakpointStyle = 0; + continueAfterActions = 0; + delayBeforeContinue = 0; + fileReference = 33AD831D0B80269C00CF4200 /* xpath.h */; + functionName = "operator()"; + hitCount = 1; + lineNumber = 768; + location = main.ob; + modificationTime = 192950207.974497; + state = 1; + }; + 333230A30B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82EF0B80269C00CF4200 /* fdstream.hpp */; + name = "fdstream.hpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1154; + vrLoc = 0; + }; + 333230A40B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82F60B80269C00CF4200 /* ledger.h */; + name = "ledger.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 775; + vrLoc = 0; + }; + 333230A70B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82F40B80269C00CF4200 /* journal.cc */; + name = "journal.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 723; + vrLoc = 0; + }; + 333230A80B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E10B80269C00CF4200 /* binary.h */; + name = "binary.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 839; + vrLoc = 0; + }; + 333230A90B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E00B80269C00CF4200 /* binary.cc */; + name = "binary.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1013; + vrLoc = 0; + }; + 333230AA0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83180B80269C00CF4200 /* xml.cc */; + name = "xml.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 912; + vrLoc = 0; + }; + 333230AB0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83070B80269C00CF4200 /* reconcile.cc */; + name = "reconcile.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 0; + vrLoc = 0; + }; + 333230AC0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E80B80269C00CF4200 /* derive.cc */; + name = "derive.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 934; + vrLoc = 0; + }; + 333230AD0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E20B80269C00CF4200 /* csv.cc */; + name = "csv.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 0; + vrLoc = 0; + }; + 333230AF0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 3356EA090B8029FA00EC228D /* option.cc */; + name = "option.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 794; + vrLoc = 0; + }; + 333230B20B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82EF0B80269C00CF4200 /* fdstream.hpp */; + name = "fdstream.hpp: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 1154; + vrLoc = 0; + }; + 333230B40B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82F60B80269C00CF4200 /* ledger.h */; + name = "ledger.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 775; + vrLoc = 0; + }; + 333230BA0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E10B80269C00CF4200 /* binary.h */; + name = "binary.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 839; + vrLoc = 0; + }; + 333230BE0B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E80B80269C00CF4200 /* derive.cc */; + name = "derive.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 934; + vrLoc = 0; + }; + 333230C00B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 3356EA000B80299700EC228D /* main.cc */; + name = "main.cc: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 696; + vrLoc = 0; + }; + 333230C20B802D4000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83190B80269C00CF4200 /* xml.h */; + name = "}"; + rLen = 4; + rLoc = 1896; + rType = 0; + vrLen = 1033; + vrLoc = 1373; + }; + 333231000B802FF000C403F5 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 3356EA000B80299700EC228D /* main.cc */; + name = "ledger::tracing_active = true;"; + rLen = 35; + rLoc = 10634; + rType = 0; + vrLen = 718; + vrLoc = 10521; + }; + 333231030B802FF000C403F5 /* XCBreakpointsBucket */ = { + isa = XCBreakpointsBucket; + name = "Project Breakpoints"; + objects = ( + 333230A20B802D3E00C403F5 /* xpath.h:768 */, + ); + }; + 3356EA000B80299700EC228D /* main.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {825, 8910}}"; + sepNavSelRange = "{10634, 0}"; + sepNavVisRect = "{{0, 7305}, {825, 384}}"; + }; + }; + 3356EA090B8029FA00EC228D /* option.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {767, 5202}}"; + sepNavSelRange = "{2917, 0}"; + sepNavVisRect = "{{0, 2231}, {689, 236}}"; + }; + }; + 3357D0B80BD4A651004B3223 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; + name = "static std::time_t base = -1;"; + rLen = 32; + rLoc = 550; + rType = 0; + vrLen = 473; + vrLoc = 240; + }; + 3357D0B90BD4A651004B3223 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + comments = "warning: control reaches end of non-void function"; + fRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; + rLen = 1; + rLoc = 1226; + rType = 1; + }; + 3357D0BA0BD4A651004B3223 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; + name = "static std::time_t base = -1;"; + rLen = 32; + rLoc = 550; + rType = 0; + vrLen = 473; + vrLoc = 240; + }; + 3357D0BB0BD4A651004B3223 /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; + name = "amount.cc: 2046"; + rLen = 0; + rLoc = 51254; + rType = 0; + vrLen = 580; + vrLoc = 29938; + }; + 33AD82D60B80262200CF4200 /* ledger */ = { + isa = PBXExecutable; + activeArgIndex = 0; + activeArgIndices = ( + YES, + YES, + YES, + ); + argumentStrings = ( + "-f", + /home/johnw/doc/Finances/ledger.dat, + xml, + ); + autoAttachOnCrash = 1; + configStateDict = { + }; + customDataFormattersEnabled = 1; + debuggerPlugin = GDBDebugging; + disassemblyDisplayState = 0; + dylibVariantSuffix = ""; + enableDebugStr = 1; + environmentEntries = ( + ); + executableSystemSymbolLevel = 0; + executableUserSymbolLevel = 0; + libgmallocEnabled = 0; + name = ledger; + savedGlobals = { + }; + sourceDirectories = ( + ); + variableFormatDictionary = { + }; + }; + 33AD82DA0B80264000CF4200 /* Source Control */ = { + isa = PBXSourceControlManager; + fallbackIsa = XCSourceControlManager; + isSCMEnabled = 0; + scmConfiguration = { + SubversionToolPath = /usr/local/bin/svn; + }; + scmType = ""; + }; + 33AD82DB0B80264000CF4200 /* Code sense */ = { + isa = PBXCodeSenseManager; + indexTemplatePath = ""; + }; + 33AD82DC0B80269C00CF4200 /* amount.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {794, 36828}}"; + sepNavSelRange = "{51254, 0}"; + sepNavVisRect = "{{0, 21942}, {689, 236}}"; + }; + }; + 33AD82DD0B80269C00CF4200 /* amount.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 11178}}"; + sepNavSelRange = "{920, 30}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82DF0B80269C00CF4200 /* balance.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {686, 17244}}"; + sepNavSelRange = "{206, 16}"; + sepNavVisRect = "{{0, 0}, {337, 199}}"; + }; + }; + 33AD82E00B80269C00CF4200 /* binary.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 18378}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82E10B80269C00CF4200 /* binary.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 4662}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82E20B80269C00CF4200 /* csv.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 745}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82E40B80269C00CF4200 /* datetime.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 10368}}"; + sepNavSelRange = "{550, 32}"; + sepNavVisRect = "{{0, 219}, {792, 512}}"; + }; + }; + 33AD82E80B80269C00CF4200 /* derive.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 3294}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82EF0B80269C00CF4200 /* fdstream.hpp */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 3330}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82F00B80269C00CF4200 /* format.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 4770}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD82F20B80269C00CF4200 /* gnucash.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {749, 6912}}"; + sepNavSelRange = "{11616, 0}"; + sepNavVisRect = "{{0, 6522}, {459, 186}}"; + }; + }; + 33AD82F40B80269C00CF4200 /* journal.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {776, 18828}}"; + sepNavSelRange = "{14713, 0}"; + sepNavVisRect = "{{41, 10857}, {459, 186}}"; + }; + }; + 33AD82F60B80269C00CF4200 /* ledger.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 846}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD83050B80269C00CF4200 /* quotes.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 1566}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD83070B80269C00CF4200 /* reconcile.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 745}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD83090B80269C00CF4200 /* report.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 3852}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 745}}"; + }; + }; + 33AD830D0B80269C00CF4200 /* textual.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {830, 16146}}"; + sepNavSelRange = "{13898, 0}"; + sepNavVisRect = "{{0, 10086}, {459, 186}}"; + }; + }; + 33AD83160B80269C00CF4200 /* value.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {776, 47790}}"; + sepNavSelRange = "{51277, 0}"; + sepNavVisRect = "{{0, 33738}, {459, 186}}"; + }; + }; + 33AD83170B80269C00CF4200 /* value.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {722, 10170}}"; + sepNavSelRange = "{702, 14}"; + sepNavVisRect = "{{0, 360}, {337, 199}}"; + }; + }; + 33AD83180B80269C00CF4200 /* xml.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {812, 8442}}"; + sepNavSelRange = "{7071, 0}"; + sepNavVisRect = "{{0, 5687}, {689, 236}}"; + }; + }; + 33AD83190B80269C00CF4200 /* xml.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 6048}}"; + sepNavSelRange = "{1896, 4}"; + sepNavVisRect = "{{0, 1463}, {792, 512}}"; + }; + }; + 33AD831A0B80269C00CF4200 /* xmlparse.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {740, 8514}}"; + sepNavSelRange = "{5476, 0}"; + sepNavVisRect = "{{0, 3545}, {689, 236}}"; + }; + }; + 33AD831C0B80269C00CF4200 /* xpath.cc */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {749, 45954}}"; + sepNavSelRange = "{46916, 0}"; + sepNavVisRect = "{{0, 35124}, {459, 186}}"; + }; + }; + 33AD831D0B80269C00CF4200 /* xpath.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {785, 13968}}"; + sepNavSelRange = "{7507, 0}"; + sepNavVisRect = "{{0, 5550}, {459, 186}}"; + }; + }; + 33AD83750B80280B00CF4200 /* acconf.h */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {792, 1674}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 512}}"; + }; + }; + 33B8460B0BD0A5CC00472F4E /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD831D0B80269C00CF4200 /* xpath.h */; + name = "xpath.h: 774"; + rLen = 0; + rLoc = 18463; + rType = 0; + vrLen = 613; + vrLoc = 17535; + }; + 33B8460D0BD0A5CC00472F4E /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD831D0B80269C00CF4200 /* xpath.h */; + name = "xpath.h: 774"; + rLen = 0; + rLoc = 18463; + rType = 0; + vrLen = 613; + vrLoc = 17535; + }; + 33B846130BD0A63200472F4E /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83750B80280B00CF4200 /* acconf.h */; + name = "acconf.h: 1"; + rLen = 0; + rLoc = 0; + rType = 0; + vrLen = 840; + vrLoc = 0; + }; + 33B846400BD0A6EB00472F4E /* PBXTextBookmark */ = { + isa = PBXTextBookmark; + fRef = 33AD83190B80269C00CF4200 /* xml.h */; + name = "}"; + rLen = 4; + rLoc = 1896; + rType = 0; + vrLen = 613; + vrLoc = 1552; + }; + 8DD76F620486A84900D96B5E /* ledger */ = { + activeExec = 0; + executables = ( + 33AD82D60B80262200CF4200 /* ledger */, + ); + }; + C6859E8B029090EE04C91782 /* ledger.1 */ = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {821, 1422}}"; + sepNavSelRange = "{0, 0}"; + sepNavVisRect = "{{0, 0}, {792, 512}}"; + }; + }; +} diff --git a/contrib/ledger.xcodeproj/project.pbxproj b/contrib/ledger.xcodeproj/project.pbxproj new file mode 100644 index 00000000..1cdfe32b --- /dev/null +++ b/contrib/ledger.xcodeproj/project.pbxproj @@ -0,0 +1,584 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 3356EA010B80299700EC228D /* main.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3356EA000B80299700EC228D /* main.cc */; }; + 3356EA0A0B8029FA00EC228D /* option.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3356EA090B8029FA00EC228D /* option.cc */; }; + 3357D09C0BD4A3FD004B3223 /* libgmp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3357D09B0BD4A3FD004B3223 /* libgmp.dylib */; }; + 3357D09E0BD4A40E004B3223 /* libexpat.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3357D09D0BD4A40E004B3223 /* libexpat.dylib */; }; + 33AD831E0B80269C00CF4200 /* amount.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; }; + 33AD831F0B80269C00CF4200 /* amount.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82DD0B80269C00CF4200 /* amount.h */; }; + 33AD83200B80269C00CF4200 /* balance.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82DE0B80269C00CF4200 /* balance.cc */; }; + 33AD83210B80269C00CF4200 /* balance.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82DF0B80269C00CF4200 /* balance.h */; }; + 33AD83220B80269C00CF4200 /* binary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E00B80269C00CF4200 /* binary.cc */; }; + 33AD83230B80269C00CF4200 /* binary.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E10B80269C00CF4200 /* binary.h */; }; + 33AD83240B80269C00CF4200 /* csv.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E20B80269C00CF4200 /* csv.cc */; }; + 33AD83250B80269C00CF4200 /* csv.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E30B80269C00CF4200 /* csv.h */; }; + 33AD83260B80269C00CF4200 /* datetime.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; }; + 33AD83270B80269C00CF4200 /* datetime.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E50B80269C00CF4200 /* datetime.h */; }; + 33AD83280B80269C00CF4200 /* debug.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E60B80269C00CF4200 /* debug.cc */; }; + 33AD83290B80269C00CF4200 /* debug.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E70B80269C00CF4200 /* debug.h */; }; + 33AD832A0B80269C00CF4200 /* derive.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E80B80269C00CF4200 /* derive.cc */; }; + 33AD832B0B80269C00CF4200 /* derive.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E90B80269C00CF4200 /* derive.h */; }; + 33AD832E0B80269C00CF4200 /* emacs.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82EC0B80269C00CF4200 /* emacs.cc */; }; + 33AD832F0B80269C00CF4200 /* emacs.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82ED0B80269C00CF4200 /* emacs.h */; }; + 33AD83300B80269C00CF4200 /* error.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82EE0B80269C00CF4200 /* error.h */; }; + 33AD83310B80269C00CF4200 /* fdstream.hpp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82EF0B80269C00CF4200 /* fdstream.hpp */; }; + 33AD83320B80269C00CF4200 /* format.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F00B80269C00CF4200 /* format.cc */; }; + 33AD83330B80269C00CF4200 /* format.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F10B80269C00CF4200 /* format.h */; }; + 33AD83340B80269C00CF4200 /* gnucash.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F20B80269C00CF4200 /* gnucash.cc */; }; + 33AD83350B80269C00CF4200 /* gnucash.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F30B80269C00CF4200 /* gnucash.h */; }; + 33AD83360B80269C00CF4200 /* journal.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F40B80269C00CF4200 /* journal.cc */; }; + 33AD83370B80269C00CF4200 /* journal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F50B80269C00CF4200 /* journal.h */; }; + 33AD83380B80269C00CF4200 /* ledger.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F60B80269C00CF4200 /* ledger.h */; }; + 33AD83390B80269C00CF4200 /* mask.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F70B80269C00CF4200 /* mask.cc */; }; + 33AD833A0B80269C00CF4200 /* mask.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F80B80269C00CF4200 /* mask.h */; }; + 33AD833D0B80269C00CF4200 /* option.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82FB0B80269C00CF4200 /* option.h */; }; + 33AD833E0B80269C00CF4200 /* parser.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82FC0B80269C00CF4200 /* parser.cc */; }; + 33AD833F0B80269C00CF4200 /* parser.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82FD0B80269C00CF4200 /* parser.h */; }; + 33AD83450B80269C00CF4200 /* qif.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83030B80269C00CF4200 /* qif.cc */; }; + 33AD83460B80269C00CF4200 /* qif.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83040B80269C00CF4200 /* qif.h */; }; + 33AD83470B80269C00CF4200 /* quotes.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83050B80269C00CF4200 /* quotes.cc */; }; + 33AD83480B80269C00CF4200 /* quotes.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83060B80269C00CF4200 /* quotes.h */; }; + 33AD83490B80269C00CF4200 /* reconcile.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83070B80269C00CF4200 /* reconcile.cc */; }; + 33AD834A0B80269C00CF4200 /* reconcile.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83080B80269C00CF4200 /* reconcile.h */; }; + 33AD834B0B80269C00CF4200 /* report.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83090B80269C00CF4200 /* report.cc */; }; + 33AD834C0B80269C00CF4200 /* report.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830A0B80269C00CF4200 /* report.h */; }; + 33AD834D0B80269C00CF4200 /* session.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD830B0B80269C00CF4200 /* session.cc */; }; + 33AD834E0B80269C00CF4200 /* session.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830C0B80269C00CF4200 /* session.h */; }; + 33AD834F0B80269C00CF4200 /* textual.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD830D0B80269C00CF4200 /* textual.cc */; }; + 33AD83500B80269C00CF4200 /* textual.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830E0B80269C00CF4200 /* textual.h */; }; + 33AD83510B80269C00CF4200 /* timing.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830F0B80269C00CF4200 /* timing.h */; }; + 33AD83520B80269C00CF4200 /* trace.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83100B80269C00CF4200 /* trace.cc */; }; + 33AD83530B80269C00CF4200 /* trace.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83110B80269C00CF4200 /* trace.h */; }; + 33AD83540B80269C00CF4200 /* transform.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83120B80269C00CF4200 /* transform.cc */; }; + 33AD83550B80269C00CF4200 /* transform.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83130B80269C00CF4200 /* transform.h */; }; + 33AD83560B80269C00CF4200 /* util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83140B80269C00CF4200 /* util.cc */; }; + 33AD83570B80269C00CF4200 /* util.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83150B80269C00CF4200 /* util.h */; }; + 33AD83580B80269C00CF4200 /* value.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83160B80269C00CF4200 /* value.cc */; }; + 33AD83590B80269C00CF4200 /* value.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83170B80269C00CF4200 /* value.h */; }; + 33AD835A0B80269C00CF4200 /* xml.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83180B80269C00CF4200 /* xml.cc */; }; + 33AD835B0B80269C00CF4200 /* xml.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83190B80269C00CF4200 /* xml.h */; }; + 33AD835C0B80269C00CF4200 /* xmlparse.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD831A0B80269C00CF4200 /* xmlparse.cc */; }; + 33AD835D0B80269C00CF4200 /* xmlparse.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD831B0B80269C00CF4200 /* xmlparse.h */; }; + 33AD835E0B80269C00CF4200 /* xpath.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD831C0B80269C00CF4200 /* xpath.cc */; }; + 33AD835F0B80269C00CF4200 /* xpath.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD831D0B80269C00CF4200 /* xpath.h */; }; + 33AD83760B80280B00CF4200 /* acconf.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83750B80280B00CF4200 /* acconf.h */; }; + 33B846080BD0A5B200472F4E /* libpcre.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 33B846060BD0A5B200472F4E /* libpcre.dylib */; }; + 8DD76F6A0486A84900D96B5E /* ledger.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859E8B029090EE04C91782 /* ledger.1 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F690486A84900D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 8DD76F6A0486A84900D96B5E /* ledger.1 in CopyFiles */, + 33AD831F0B80269C00CF4200 /* amount.h in CopyFiles */, + 33AD83210B80269C00CF4200 /* balance.h in CopyFiles */, + 33AD83230B80269C00CF4200 /* binary.h in CopyFiles */, + 33AD83250B80269C00CF4200 /* csv.h in CopyFiles */, + 33AD83270B80269C00CF4200 /* datetime.h in CopyFiles */, + 33AD83290B80269C00CF4200 /* debug.h in CopyFiles */, + 33AD832B0B80269C00CF4200 /* derive.h in CopyFiles */, + 33AD832F0B80269C00CF4200 /* emacs.h in CopyFiles */, + 33AD83300B80269C00CF4200 /* error.h in CopyFiles */, + 33AD83310B80269C00CF4200 /* fdstream.hpp in CopyFiles */, + 33AD83330B80269C00CF4200 /* format.h in CopyFiles */, + 33AD83350B80269C00CF4200 /* gnucash.h in CopyFiles */, + 33AD83370B80269C00CF4200 /* journal.h in CopyFiles */, + 33AD83380B80269C00CF4200 /* ledger.h in CopyFiles */, + 33AD833A0B80269C00CF4200 /* mask.h in CopyFiles */, + 33AD833D0B80269C00CF4200 /* option.h in CopyFiles */, + 33AD833F0B80269C00CF4200 /* parser.h in CopyFiles */, + 33AD83460B80269C00CF4200 /* qif.h in CopyFiles */, + 33AD83480B80269C00CF4200 /* quotes.h in CopyFiles */, + 33AD834A0B80269C00CF4200 /* reconcile.h in CopyFiles */, + 33AD834C0B80269C00CF4200 /* report.h in CopyFiles */, + 33AD834E0B80269C00CF4200 /* session.h in CopyFiles */, + 33AD83500B80269C00CF4200 /* textual.h in CopyFiles */, + 33AD83510B80269C00CF4200 /* timing.h in CopyFiles */, + 33AD83530B80269C00CF4200 /* trace.h in CopyFiles */, + 33AD83550B80269C00CF4200 /* transform.h in CopyFiles */, + 33AD83570B80269C00CF4200 /* util.h in CopyFiles */, + 33AD83590B80269C00CF4200 /* value.h in CopyFiles */, + 33AD835B0B80269C00CF4200 /* xml.h in CopyFiles */, + 33AD835D0B80269C00CF4200 /* xmlparse.h in CopyFiles */, + 33AD835F0B80269C00CF4200 /* xpath.h in CopyFiles */, + 33AD83760B80280B00CF4200 /* acconf.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 3356EA000B80299700EC228D /* main.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cc; sourceTree = ""; }; + 3356EA090B8029FA00EC228D /* option.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = option.cc; sourceTree = ""; }; + 3357D09B0BD4A3FD004B3223 /* libgmp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libgmp.dylib; path = /sw/lib/libgmp.dylib; sourceTree = ""; }; + 3357D09D0BD4A40E004B3223 /* libexpat.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libexpat.dylib; path = /usr/local/lib/libexpat.dylib; sourceTree = ""; }; + 33AD82DC0B80269C00CF4200 /* amount.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = amount.cc; sourceTree = ""; }; + 33AD82DD0B80269C00CF4200 /* amount.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = amount.h; sourceTree = ""; }; + 33AD82DE0B80269C00CF4200 /* balance.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = balance.cc; sourceTree = ""; }; + 33AD82DF0B80269C00CF4200 /* balance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = balance.h; sourceTree = ""; }; + 33AD82E00B80269C00CF4200 /* binary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = binary.cc; sourceTree = ""; }; + 33AD82E10B80269C00CF4200 /* binary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = binary.h; sourceTree = ""; }; + 33AD82E20B80269C00CF4200 /* csv.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = csv.cc; sourceTree = ""; }; + 33AD82E30B80269C00CF4200 /* csv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = csv.h; sourceTree = ""; }; + 33AD82E40B80269C00CF4200 /* datetime.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = datetime.cc; sourceTree = ""; }; + 33AD82E50B80269C00CF4200 /* datetime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = datetime.h; sourceTree = ""; }; + 33AD82E60B80269C00CF4200 /* debug.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debug.cc; sourceTree = ""; }; + 33AD82E70B80269C00CF4200 /* debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = ""; }; + 33AD82E80B80269C00CF4200 /* derive.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = derive.cc; sourceTree = ""; }; + 33AD82E90B80269C00CF4200 /* derive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = derive.h; sourceTree = ""; }; + 33AD82EC0B80269C00CF4200 /* emacs.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emacs.cc; sourceTree = ""; }; + 33AD82ED0B80269C00CF4200 /* emacs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = emacs.h; sourceTree = ""; }; + 33AD82EE0B80269C00CF4200 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = ""; }; + 33AD82EF0B80269C00CF4200 /* fdstream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = fdstream.hpp; sourceTree = ""; }; + 33AD82F00B80269C00CF4200 /* format.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = format.cc; sourceTree = ""; }; + 33AD82F10B80269C00CF4200 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = format.h; sourceTree = ""; }; + 33AD82F20B80269C00CF4200 /* gnucash.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = gnucash.cc; sourceTree = ""; }; + 33AD82F30B80269C00CF4200 /* gnucash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gnucash.h; sourceTree = ""; }; + 33AD82F40B80269C00CF4200 /* journal.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = journal.cc; sourceTree = ""; }; + 33AD82F50B80269C00CF4200 /* journal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = journal.h; sourceTree = ""; }; + 33AD82F60B80269C00CF4200 /* ledger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ledger.h; sourceTree = ""; }; + 33AD82F70B80269C00CF4200 /* mask.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mask.cc; sourceTree = ""; }; + 33AD82F80B80269C00CF4200 /* mask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mask.h; sourceTree = ""; }; + 33AD82FB0B80269C00CF4200 /* option.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = option.h; sourceTree = ""; }; + 33AD82FC0B80269C00CF4200 /* parser.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parser.cc; sourceTree = ""; }; + 33AD82FD0B80269C00CF4200 /* parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = ""; }; + 33AD83030B80269C00CF4200 /* qif.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = qif.cc; sourceTree = ""; }; + 33AD83040B80269C00CF4200 /* qif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = qif.h; sourceTree = ""; }; + 33AD83050B80269C00CF4200 /* quotes.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = quotes.cc; sourceTree = ""; }; + 33AD83060B80269C00CF4200 /* quotes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = quotes.h; sourceTree = ""; }; + 33AD83070B80269C00CF4200 /* reconcile.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reconcile.cc; sourceTree = ""; }; + 33AD83080B80269C00CF4200 /* reconcile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reconcile.h; sourceTree = ""; }; + 33AD83090B80269C00CF4200 /* report.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = report.cc; sourceTree = ""; }; + 33AD830A0B80269C00CF4200 /* report.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = report.h; sourceTree = ""; }; + 33AD830B0B80269C00CF4200 /* session.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = session.cc; sourceTree = ""; }; + 33AD830C0B80269C00CF4200 /* session.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = session.h; sourceTree = ""; }; + 33AD830D0B80269C00CF4200 /* textual.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = textual.cc; sourceTree = ""; }; + 33AD830E0B80269C00CF4200 /* textual.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = textual.h; sourceTree = ""; }; + 33AD830F0B80269C00CF4200 /* timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timing.h; sourceTree = ""; }; + 33AD83100B80269C00CF4200 /* trace.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = trace.cc; sourceTree = ""; }; + 33AD83110B80269C00CF4200 /* trace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trace.h; sourceTree = ""; }; + 33AD83120B80269C00CF4200 /* transform.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = transform.cc; sourceTree = ""; }; + 33AD83130B80269C00CF4200 /* transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = transform.h; sourceTree = ""; }; + 33AD83140B80269C00CF4200 /* util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = util.cc; sourceTree = ""; }; + 33AD83150B80269C00CF4200 /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = util.h; sourceTree = ""; }; + 33AD83160B80269C00CF4200 /* value.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = value.cc; sourceTree = ""; }; + 33AD83170B80269C00CF4200 /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = value.h; sourceTree = ""; }; + 33AD83180B80269C00CF4200 /* xml.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xml.cc; sourceTree = ""; }; + 33AD83190B80269C00CF4200 /* xml.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xml.h; sourceTree = ""; }; + 33AD831A0B80269C00CF4200 /* xmlparse.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xmlparse.cc; sourceTree = ""; }; + 33AD831B0B80269C00CF4200 /* xmlparse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xmlparse.h; sourceTree = ""; }; + 33AD831C0B80269C00CF4200 /* xpath.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xpath.cc; sourceTree = ""; }; + 33AD831D0B80269C00CF4200 /* xpath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xpath.h; sourceTree = ""; }; + 33AD83750B80280B00CF4200 /* acconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = acconf.h; sourceTree = ""; }; + 33B846060BD0A5B200472F4E /* libpcre.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpcre.dylib; path = /usr/local/lib/libpcre.dylib; sourceTree = ""; }; + 8DD76F6C0486A84900D96B5E /* ledger */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "compiled.mach-o.executable"; path = ledger; sourceTree = BUILT_PRODUCTS_DIR; }; + C6859E8B029090EE04C91782 /* ledger.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = ledger.1; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F660486A84900D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 33B846080BD0A5B200472F4E /* libpcre.dylib in Frameworks */, + 3357D09C0BD4A3FD004B3223 /* libgmp.dylib in Frameworks */, + 3357D09E0BD4A40E004B3223 /* libexpat.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* ledger */ = { + isa = PBXGroup; + children = ( + 08FB7795FE84155DC02AAC07 /* Source */, + C6859E8C029090F304C91782 /* Documentation */, + 33B8460F0BD0A60100472F4E /* Dependencies */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = ledger; + sourceTree = ""; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 333230420B802B3A00C403F5 /* Utility code */, + 333230470B802B4700C403F5 /* Core numerics */, + 3332304B0B802B5500C403F5 /* Journal representation */, + 3332304F0B802B6500C403F5 /* XML meta-representation */, + 333230530B802B7400C403F5 /* Transformations */, + 333230570B802B8200C403F5 /* Reporting */, + 333230590B802B8E00C403F5 /* Command-line driver */, + 3332305B0B802B9E00C403F5 /* Python scripting */, + ); + name = Source; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76F6C0486A84900D96B5E /* ledger */, + ); + name = Products; + sourceTree = ""; + }; + 333230420B802B3A00C403F5 /* Utility code */ = { + isa = PBXGroup; + children = ( + 33AD82F60B80269C00CF4200 /* ledger.h */, + 33AD83140B80269C00CF4200 /* util.cc */, + 33AD83150B80269C00CF4200 /* util.h */, + 33AD83750B80280B00CF4200 /* acconf.h */, + 33AD82E60B80269C00CF4200 /* debug.cc */, + 33AD82E70B80269C00CF4200 /* debug.h */, + 33AD82EE0B80269C00CF4200 /* error.h */, + 33AD830F0B80269C00CF4200 /* timing.h */, + 33AD83100B80269C00CF4200 /* trace.cc */, + 33AD83110B80269C00CF4200 /* trace.h */, + ); + name = "Utility code"; + sourceTree = ""; + }; + 333230470B802B4700C403F5 /* Core numerics */ = { + isa = PBXGroup; + children = ( + 3332306A0B802BC800C403F5 /* Common data types */, + 333230670B802BC800C403F5 /* Amounts and values */, + ); + name = "Core numerics"; + sourceTree = ""; + }; + 3332304B0B802B5500C403F5 /* Journal representation */ = { + isa = PBXGroup; + children = ( + 33AD82F40B80269C00CF4200 /* journal.cc */, + 33AD82F50B80269C00CF4200 /* journal.h */, + 33AD82FC0B80269C00CF4200 /* parser.cc */, + 33AD82FD0B80269C00CF4200 /* parser.h */, + 333230630B802BB200C403F5 /* Input formats */, + ); + name = "Journal representation"; + sourceTree = ""; + }; + 3332304F0B802B6500C403F5 /* XML meta-representation */ = { + isa = PBXGroup; + children = ( + 33AD83180B80269C00CF4200 /* xml.cc */, + 33AD83190B80269C00CF4200 /* xml.h */, + 33AD831C0B80269C00CF4200 /* xpath.cc */, + 33AD831D0B80269C00CF4200 /* xpath.h */, + ); + name = "XML meta-representation"; + sourceTree = ""; + }; + 333230530B802B7400C403F5 /* Transformations */ = { + isa = PBXGroup; + children = ( + 33AD83070B80269C00CF4200 /* reconcile.cc */, + 33AD83080B80269C00CF4200 /* reconcile.h */, + 33AD83120B80269C00CF4200 /* transform.cc */, + 33AD83130B80269C00CF4200 /* transform.h */, + ); + name = Transformations; + sourceTree = ""; + }; + 333230570B802B8200C403F5 /* Reporting */ = { + isa = PBXGroup; + children = ( + 33AD82E80B80269C00CF4200 /* derive.cc */, + 33AD82E90B80269C00CF4200 /* derive.h */, + 33AD82F00B80269C00CF4200 /* format.cc */, + 33AD82F10B80269C00CF4200 /* format.h */, + 33AD83090B80269C00CF4200 /* report.cc */, + 33AD830A0B80269C00CF4200 /* report.h */, + 3332305F0B802BAA00C403F5 /* Output formats */, + ); + name = Reporting; + sourceTree = ""; + }; + 333230590B802B8E00C403F5 /* Command-line driver */ = { + isa = PBXGroup; + children = ( + 3356EA000B80299700EC228D /* main.cc */, + 3356EA090B8029FA00EC228D /* option.cc */, + 33AD82FB0B80269C00CF4200 /* option.h */, + 33AD830B0B80269C00CF4200 /* session.cc */, + 33AD830C0B80269C00CF4200 /* session.h */, + 33AD82EF0B80269C00CF4200 /* fdstream.hpp */, + ); + name = "Command-line driver"; + sourceTree = ""; + }; + 3332305B0B802B9E00C403F5 /* Python scripting */ = { + isa = PBXGroup; + children = ( + ); + name = "Python scripting"; + sourceTree = ""; + }; + 3332305F0B802BAA00C403F5 /* Output formats */ = { + isa = PBXGroup; + children = ( + 33AD82E20B80269C00CF4200 /* csv.cc */, + 33AD82E30B80269C00CF4200 /* csv.h */, + 33AD82EC0B80269C00CF4200 /* emacs.cc */, + 33AD82ED0B80269C00CF4200 /* emacs.h */, + ); + name = "Output formats"; + sourceTree = ""; + }; + 333230630B802BB200C403F5 /* Input formats */ = { + isa = PBXGroup; + children = ( + 33AD82E00B80269C00CF4200 /* binary.cc */, + 33AD82E10B80269C00CF4200 /* binary.h */, + 33AD82F20B80269C00CF4200 /* gnucash.cc */, + 33AD82F30B80269C00CF4200 /* gnucash.h */, + 33AD83030B80269C00CF4200 /* qif.cc */, + 33AD83040B80269C00CF4200 /* qif.h */, + 33AD830D0B80269C00CF4200 /* textual.cc */, + 33AD830E0B80269C00CF4200 /* textual.h */, + 33AD831A0B80269C00CF4200 /* xmlparse.cc */, + 33AD831B0B80269C00CF4200 /* xmlparse.h */, + ); + name = "Input formats"; + sourceTree = ""; + }; + 333230670B802BC800C403F5 /* Amounts and values */ = { + isa = PBXGroup; + children = ( + 33AD82DC0B80269C00CF4200 /* amount.cc */, + 33AD82DD0B80269C00CF4200 /* amount.h */, + 33AD82DE0B80269C00CF4200 /* balance.cc */, + 33AD82DF0B80269C00CF4200 /* balance.h */, + 33AD83160B80269C00CF4200 /* value.cc */, + 33AD83170B80269C00CF4200 /* value.h */, + 33AD83050B80269C00CF4200 /* quotes.cc */, + 33AD83060B80269C00CF4200 /* quotes.h */, + ); + name = "Amounts and values"; + sourceTree = ""; + }; + 3332306A0B802BC800C403F5 /* Common data types */ = { + isa = PBXGroup; + children = ( + 33AD82E40B80269C00CF4200 /* datetime.cc */, + 33AD82E50B80269C00CF4200 /* datetime.h */, + 33AD82F70B80269C00CF4200 /* mask.cc */, + 33AD82F80B80269C00CF4200 /* mask.h */, + ); + name = "Common data types"; + sourceTree = ""; + }; + 33B8460F0BD0A60100472F4E /* Dependencies */ = { + isa = PBXGroup; + children = ( + 3357D09D0BD4A40E004B3223 /* libexpat.dylib */, + 3357D09B0BD4A3FD004B3223 /* libgmp.dylib */, + 33B846060BD0A5B200472F4E /* libpcre.dylib */, + ); + name = Dependencies; + sourceTree = ""; + }; + C6859E8C029090F304C91782 /* Documentation */ = { + isa = PBXGroup; + children = ( + C6859E8B029090EE04C91782 /* ledger.1 */, + ); + name = Documentation; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F620486A84900D96B5E /* ledger */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "ledger" */; + buildPhases = ( + 8DD76F640486A84900D96B5E /* Sources */, + 8DD76F660486A84900D96B5E /* Frameworks */, + 8DD76F690486A84900D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ledger; + productInstallPath = "$(HOME)/bin"; + productName = ledger; + productReference = 8DD76F6C0486A84900D96B5E /* ledger */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "ledger" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* ledger */; + projectDirPath = ""; + targets = ( + 8DD76F620486A84900D96B5E /* ledger */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F640486A84900D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33AD831E0B80269C00CF4200 /* amount.cc in Sources */, + 33AD83200B80269C00CF4200 /* balance.cc in Sources */, + 33AD83220B80269C00CF4200 /* binary.cc in Sources */, + 33AD83240B80269C00CF4200 /* csv.cc in Sources */, + 33AD83260B80269C00CF4200 /* datetime.cc in Sources */, + 33AD83280B80269C00CF4200 /* debug.cc in Sources */, + 33AD832A0B80269C00CF4200 /* derive.cc in Sources */, + 33AD832E0B80269C00CF4200 /* emacs.cc in Sources */, + 33AD83320B80269C00CF4200 /* format.cc in Sources */, + 33AD83340B80269C00CF4200 /* gnucash.cc in Sources */, + 33AD83360B80269C00CF4200 /* journal.cc in Sources */, + 33AD83390B80269C00CF4200 /* mask.cc in Sources */, + 33AD833E0B80269C00CF4200 /* parser.cc in Sources */, + 33AD83450B80269C00CF4200 /* qif.cc in Sources */, + 33AD83470B80269C00CF4200 /* quotes.cc in Sources */, + 33AD83490B80269C00CF4200 /* reconcile.cc in Sources */, + 33AD834B0B80269C00CF4200 /* report.cc in Sources */, + 33AD834D0B80269C00CF4200 /* session.cc in Sources */, + 33AD834F0B80269C00CF4200 /* textual.cc in Sources */, + 33AD83520B80269C00CF4200 /* trace.cc in Sources */, + 33AD83540B80269C00CF4200 /* transform.cc in Sources */, + 33AD83560B80269C00CF4200 /* util.cc in Sources */, + 33AD83580B80269C00CF4200 /* value.cc in Sources */, + 33AD835A0B80269C00CF4200 /* xml.cc in Sources */, + 33AD835C0B80269C00CF4200 /* xmlparse.cc in Sources */, + 33AD835E0B80269C00CF4200 /* xpath.cc in Sources */, + 3356EA010B80299700EC228D /* main.cc in Sources */, + 3356EA0A0B8029FA00EC228D /* option.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB923208733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 3.0; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = ""; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG_LEVEL=4", + "HAVE_EXPAT=1", + ); + HEADER_SEARCH_PATHS = /usr/local/include; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /usr/local/lib, + /sw/lib, + ); + PRODUCT_NAME = ledger; + ZERO_LINK = YES; + }; + name = Debug; + }; + 1DEB923308733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + CURRENT_PROJECT_VERSION = 3.0; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = ""; + HEADER_SEARCH_PATHS = /usr/local/include; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /usr/local/lib, + /sw/lib, + ); + PRODUCT_NAME = ledger; + }; + name = Release; + }; + 1DEB923608733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + /usr/local/include, + /sw/include, + ); + LIBRARY_SEARCH_PATHS = ( + /usr/local/lib, + /sw/lib, + ); + PREBINDING = NO; + SDKROOT = ""; + }; + name = Debug; + }; + 1DEB923708733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + /usr/local/include, + /sw/include, + ); + LIBRARY_SEARCH_PATHS = ( + /usr/local/lib, + /sw/lib, + ); + PREBINDING = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "ledger" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923208733DC60010E9CD /* Debug */, + 1DEB923308733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "ledger" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923608733DC60010E9CD /* Debug */, + 1DEB923708733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/csv.cc b/csv.cc deleted file mode 100644 index e69de29b..00000000 diff --git a/csv.h b/csv.h deleted file mode 100644 index e69de29b..00000000 diff --git a/derive.cc b/derive.cc deleted file mode 100644 index 6586c1f4..00000000 --- a/derive.cc +++ /dev/null @@ -1,178 +0,0 @@ -#include "derive.h" -#include "mask.h" - -namespace ledger { - -void derive_command::operator() - (value_t& result, xml::xpath_t::scope_t * locals) -{ -#if 0 - std::ostream& out = *get_ptr(locals, 0); - repitem_t * items = get_ptr(locals, 1); - strings_list& args = *get_ptr(locals, 2); - - std::auto_ptr added(new entry_t); - - entry_t * matching = NULL; - - strings_list::iterator i = args.begin(); - - added->_date = *i++; - if (i == args.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 == args.end() || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { - acct = journal.find_account("Expenses"); - } - else if (i != args.end()) { - acct = journal.find_account_re(*i); - if (! acct) - acct = journal.find_account(*i); - assert(acct); - i++; - } - - if (i == args.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 > 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()); - } - } - - if (journal.basket) - acct = journal.basket; - else - acct = journal.find_account("Equity"); - - added->add_transaction(new transaction_t(acct)); - } - else if (i == args.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 != args.end()) { - account_t * acct = journal.find_account_re(*i); - if (! acct) - acct = journal.find_account(*i); - assert(acct); - added->transactions.back()->account = acct; - } - } - else { - while (i != args.end()) { - 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: - if (! acct) - acct = journal.find_account_re(re_pat); - if (! acct) - acct = journal.find_account(re_pat); - - transaction_t * xact; - if (i == args.end()) { - if (amt) - xact = new transaction_t(acct, *amt); - else - xact = new transaction_t(acct); - } else { - xact = new transaction_t(acct, amount_t(*i++)); - if (! xact->amount.commodity()) { - if (amt) - xact->amount.set_commodity(amt->commodity()); - else if (commodity_t::default_commodity) - xact->amount.set_commodity(*commodity_t::default_commodity); - } - } - added->add_transaction(xact); - } - - assert(matching->transactions.back()->account); - if (account_t * draw_acct = matching->transactions.back()->account) - added->add_transaction(new transaction_t(draw_acct)); - } - - done: - if (! 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(); -#endif -} - -} // namespace ledger diff --git a/derive.h b/derive.h deleted file mode 100644 index c0607fc2..00000000 --- a/derive.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef _DERIVE_H -#define _DERIVE_H - -#include "journal.h" - -namespace ledger { - -class derive_command : public xml::xpath_t::functor_t -{ - public: - derive_command() : xml::xpath_t::functor_t("entry", true) {} - - virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); -}; - -} // namespace ledger - -#endif // _DERIVE_H diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 00000000..a03b54d4 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,275 @@ +# Doxyfile 1.5.1 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = Ledger +PROJECT_NUMBER = 3.0 +OUTPUT_DIRECTORY = %builddir%/docs +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = /Applications/Copied/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = %srcdir% +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 +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = usletter +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = YES +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = /Applications/Copied/Doxygen.app/Contents/Resources/dot +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/docs/ledger.info b/docs/ledger.info new file mode 100644 index 00000000..651f5d91 --- /dev/null +++ b/docs/ledger.info @@ -0,0 +1,3640 @@ +This is /Users/johnw/src/ledger/docs/ledger.info, produced by makeinfo +version 4.7 from /Users/johnw/src/ledger/docs/ledger.texi. + +INFO-DIR-SECTION User Applications + Copyright (c) 2003-2006, 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. + +START-INFO-DIR-ENTRY +* Ledger: (ledger). Command Line Accounting +END-INFO-DIR-ENTRY + + +File: ledger.info, Node: Top, Next: Introduction, Prev: (dir), Up: (dir) + +Overview +******** + +Copyright (c) 2003-2006, 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. + +* Menu: + +* Introduction:: +* Running Ledger:: +* Keeping a ledger:: +* Using XML:: + + +File: ledger.info, Node: Introduction, Next: Running Ledger, Prev: Top, Up: Top + +1 Introduction +************** + +Ledger is an accounting tool with the moxie to exist. It provides no +bells or whistles, and returns the user to the days before user +interfaces were even a twinkling in their father's CRT. + + What it does offer is a double-entry accounting ledger with all the +flexibility and muscle of its modern day cousins, without any of the +fat. Think of it as the Bran Muffin of accounting tools. + + To use it, you need to start keeping a ledger. This is the basis of +all accounting, and if you haven't started yet, now is the time to +learn. The little booklet that comes with your checkbook is a ledger, +so we'll describe double-entry accounting in terms of that. + + A checkbook ledger records debits (subtractions, or withdrawals) and +credits (additions, or deposits) with reference to a single account: +the checking account. Where the money comes from, and where it goes +to, are described in the payee field, where you write the person or +company's name. The ultimate aim of keeping a checkbook ledger is to +know how much money is available to spend. That's really the aim of +all ledgers. + + What computers add is the ability to walk through these transactions, +and tell you things about your spending habits; to let you devise +budgets and get control over your spending; to squirrel away money into +virtual savings account without having to physically move money around; +etc. As you keep your ledger, you are recording information about your +life and habits, and sometimes that information can start telling you +things you aren't aware of. Such is the aim of all good accounting +tools. + + The next step up from a checkbook ledger, is a ledger that keeps +track of all your accounts, not just checking. In such a ledger, you +record not only who gets paid--in the case of a debit--but where the +money came from. In a checkbook ledger, its assumed that all the money +comes from your checking account. But in a general ledger, you write +transaction two-lines: the source account and target account. _There +must always be a debit from at least one account for every credit made +to another account_. This is what is meant by "double-entry" +accounting: the ledger must always balance to zero, with an equal +number of debits and credits. + + For example, let's say you have a checking account and a brokerage +account, and you can write checks from both of them. Rather than keep +two checkbooks, you decide to use one ledger for both. In this general +ledger you need to record a payment to Pacific Bell for your monthly +phone bill. The cost is $23.00, let's say, and you want to pay it from +your checking account. In the general ledger you need to say where the +money came from, in addition to where it's going to. The entry might +look like this: + + 9/29 BAL Pacific Bell $-200.00 $-200.00 + Equity:Opening Balances $200.00 + 9/29 BAL Checking $100.00 $100.00 + Equity:Opening Balances $-100.00 + 9/29 100 Pacific Bell $23.00 $223.00 + Checking $-23.00 $77.00 + + The first line shows a payment to Pacific Bell for $23.00. Because +there is no "balance" in a general ledger--it's always zero--we write +in the total balance of all payments to "Pacific Bell", which now is +$223.00 (previously the balance was $200.00). This is done by looking +at the last entry for "Pacific Bell" in the ledger, adding $23.00 to +that amount, and writing the total in the balance column. And the +money came from "Checking"--a withdrawal of $23.00--which leaves the +ending balance in "Checking" at $77.00. This is a very manual +procedure; but that's where computers come in... + + The transaction must balance to $0: $23 went to Pacific Bell, $23 +came from Checking. There is nothing left over to be accounted for, +since the money has simply moved from one account to another. This is +the basis of double-entry accounting: that money never pops in or out of +existence; it is always a transaction from one account to another. + + Keeping a general ledger is the same as keeping two separate ledgers: +One for Pacific Bell and one for Checking. In that case, each time a +payment is written into one, you write a corresponding withdrawal into +the other. This makes it easier to write in a "running balance", since +you don't have to look back at the last time the account was +referenced--but it also means having a lot of ledger books, if you deal +with multiple accounts. + + Enter the beauty of computerized accounting. The purpose of the +Ledger program is to make general ledger accounting simple, by keeping +track of the balances for you. Your only job is to enter the +transactions. If a transaction does not balance, Ledger displays an +error and indicates the incorrect transaction.(1) + + In summary, there are two aspects of Ledger use: updating the ledger +data file, and using the Ledger tool to view the summarized result of +your entries. + + And just for the sake of example--as a starting point for those who +want to dive in head-first--here are the ledger entries from above, +formatting as the ledger program wishes to see them: + + 2004/09/29 Pacific Bell + Payable:Pacific Bell $-200.00 + Equity:Opening Balances + + 2004/09/29 Checking + Accounts:Checking $100.00 + Equity:Opening Balances + + 2004/09/29 Pacific Bell + Payable:Pacific Bell $23.00 + Accounts:Checking + + The account balances and registers in this file, if saved as +`ledger.dat', could be reported using: + + $ ledger -f ledger.dat balance + $ ledger -f ledger.dat register checking + $ ledger -f ledger.dat register bell + +* Menu: + +* Building the program:: +* Getting help:: + + ---------- Footnotes ---------- + + (1) In some special cases, it automatically balances this entry for +you. + + +File: ledger.info, Node: Building the program, Next: Getting help, Prev: Introduction, Up: Introduction + +1.1 Building the program +======================== + +Ledger is written in ANSI C++, and should compile on any platform. It +depends on the GNU multiprecision integer library (libgmp), and the +Perl regular expression library (libpcre). It was developed using GNU +make and gcc 3.3, on a PowerBook running OS/X. + + To build and install once you have these libraries on your system, +enter these commands: + + ./configure && make install + + +File: ledger.info, Node: Getting help, Prev: Building the program, Up: Introduction + +1.2 Getting help +================ + +If you need help on how to use Ledger, or run into problems, you can +just the Ledger mailing list at the following Web address: + + https://lists.sourceforge.net/lists/listinfo/ledger-discuss + + You can also find help at the `#ledger' channel on the IRC server +`irc.freenode.net'. + + +File: ledger.info, Node: Running Ledger, Next: Keeping a ledger, Prev: Introduction, Up: Top + +2 Running Ledger +**************** + +Ledger has a very simple command-line interface, named--enticing +enough--`ledger'. It supports a few reporting commands, and a large +number of options for refining the output from those commands. The +basic syntax of any ledger command is: + + ledger [OPTIONS...] COMMAND [ARGS...] + + Command options must always precede the command word. After the +command word there may appear any number of arguments. For most +commands, these arguments are regular expressions that cause the output +to relate only to transactions matching those regular expressions. For +the `entry' command, the arguments have a special meaning, described +below. + + The regular expressions arguments always match the account name that +a transaction refers to. To match on the payee of the entry instead, +precede the regular expression with `--'. For example, the following +balance command reports account totals for rent, food and movies, but +only those whose payee matches Freddie: + + ledger bal rent food movies -- freddie + + There are many, many command options available with the `ledger' +command, and it takes a while to master them. However, none of them +are required to use the basic reporting commands. + +* Menu: + +* Usage overview:: +* Commands:: +* Options:: +* Format strings:: +* Value expressions:: +* Period expressions:: +* File format:: +* Some typical queries:: +* Budgeting and forecasting:: + + +File: ledger.info, Node: Usage overview, Next: Commands, Prev: Running Ledger, Up: Running Ledger + +2.1 Usage overview +================== + +Before getting into the details of how to run Ledger, it will be easier +to introduce the features in the context of their typical usage. To +that end, this section presents a series of recipes, gradually +introducing all of the command-line features of Ledger. + + For the purpose of these examples, assume the environment variable +LEDGER is set to the file `sample.dat' (which is included in the +distribution), and that the contents of that file are: + + = /^Expenses:Books/ + (Liabilities:Taxes) -0.10 + + ~ Monthly + Assets:Bank:Checking $500.00 + Income:Salary + + 2004/05/01 * Checking balance + Assets:Bank:Checking $1,000.00 + Equity:Opening Balances + + 2004/05/01 * Investment balance + Assets:Brokerage 50 AAPL $30.00 + Equity:Opening Balances + + 2004/05/14 * Pay day + Assets:Bank:Checking $500.00 + Income:Salary + + 2004/05/27 Book Store + Expenses:Books $20.00 + Liabilities:MasterCard + + 2004/05/27 (100) Credit card company + Liabilities:MasterCard $20.00 + Assets:Bank:Checking + + This sample file demonstrates a basic principle of accounting which +it is recommended you follow: Keep all of your accounts under five +parent Assets, Liabilities, Income, Expenses and Equity. It is +important to do so in order to make sense out of the following examples. + +2.1.1 Checking balances +----------------------- + +Ledger has seven basic commands, but by far the most often used are +`balance' and `register'. To see a summary balance of all accounts, +use: + + ledger bal + + `bal' is a short-hand for `balance'. This command prints out the +summary totals of the five parent accounts used in `sample.dat': + + $1,480.00 + 50 AAPL Assets + $-2,500.00 Equity + $20.00 Expenses + $-500.00 Income + $-2.00 Liabilities + -------------------- + $-1,502.00 + 50 AAPL + + None of the child accounts are shown, just the parent account totals. +We can see that in `Assets' there is $1,480.00, and 50 shares of Apple +stock. There is also a negative grand total. Usually the grand total +is zero, which means that all accounts balance(1). In this case, since +the 50 shares of Apple stock cost $1,500.00 dollars, then these two +amounts balance each other in the grand total. The extra $2.00 comes +from a virtual transaction being added by the automatic entry at the +top of the file. The entry is virtual because the account name was +surrounded by parentheses in an automatic entry. Automatic entries +will be discussed later, but first let's remove the virtual transaction +from the balance report by using the `--real' option: + + ledger --real bal + + Now the report is: + + $1,480.00 + 50 AAPL Assets + $-2,500.00 Equity + $20.00 Expenses + $-500.00 Income + -------------------- + $-1,500.00 + 50 AAPL + + Since the liability was a virtual transaction, it has dropped from +the report and we see that final total is balanced. + + But we only know that it balances because `sample.dat' is quite +simple, and we happen to know that the 50 shares of Apple stock cost +$1,500.00. We can verify that things really balance by reporting the +Apple shares in terms of their cost, instead of their quantity. To do +this requires the `--basis', or `-B', option: + + ledger --real -B bal + + This command reports: + + $2,980.00 Assets + $-2,500.00 Equity + $20.00 Expenses + $-500.00 Income + + With the basis cost option, the grand total has disappeared, as it is +now zero. The confirms that the cost of everything balances to zero, +_which must always be true_. Reporting the real basis cost should +never yield a remainder(2). + +2.1.1.1 Sub-account balances +............................ + +The totals reported by the balance command are only the topmost parent +accounts. To see the totals of all child accounts as well, use the +`-s' option: + + ledger --real -B -s bal + + This reports: + + $2,980.00 Assets + $1,480.00 Bank:Checking + $1,500.00 Brokerage + $-2,500.00 Equity:Opening Balances + $20.00 Expenses:Books + $-500.00 Income:Salary + + This shows that the `Assets' total is made up from two child +account, but that the total for each of the other accounts comes from +one child account. + + Sometimes you may have a lot of children, nested very deeply, but +only want to report the first two levels. This can be done with a +display predicate, using a value expression. In the value expression, +`T' represents the reported total, and `l' is the display level for the +account: + + ledger --real -B -d "T&l<=2" bal + + This reports: + + $2,980.00 Assets + $1,480.00 Bank + $1,500.00 Brokerage + $-2,500.00 Equity:Opening Balances + $20.00 Expenses:Books + $-500.00 Income:Salary + + Instead of reporting `Bank:Checking' as a child of `Assets', it +report only `Bank', since that account is a nesting level of 2, while +`Checking' is at level 3. + + To review the display predicate used--`T&l<=2'--this rather terse +expression means: Display an account only if it has a non-zero total +(`T'), and its nesting level is less than or equal to 2 (`l<=2'). + +2.1.1.2 Specific account balances +................................. + +While reporting the totals for all accounts can be useful, most often +you will want to check the balance of a specific account or accounts. +To do this, put one or more account names after the balance command. +Since these names are really regular expressions, you can use partial +names if you wish: + + ledger bal checking + + Reports: + + $1,480.00 Assets:Bank:Checking + + Any number of names may be used: + + ledger bal checking broker liab + + Reports: + + $1,480.00 Assets:Bank:Checking + 50 AAPL Assets:Brokerage + $-2.00 Liabilities + + In this case no grand total is reported, because you are asking for +specific account balances. + + For those comfortable with regular expressions, any Perl regexp is +allowed: + + ledger bal ^assets.*checking ^liab + + Reports: + + $1,480.00 Assets:Bank:Checking + $-2.00 Liabilities:Taxes + +2.1.2 The register report +------------------------- + +While the `balance' command can be very handy for checking account +totals, by far the most powerful of Ledger's reporting tools is the +`register' command. In fact, internally both commands use the same +logic, but report the results differently: `balance' shows the summary +totals, while `register' reports each transaction and how it +contributes to that total. + + Paradoxically, the most basic form of `register' is almost never +used, since it displays every transaction: + + ledger reg + + `reg' is a short-hand for `register'. This command reports: + + 2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 + Equity:Opening Balan.. $-1,000.00 0 + 2004/05/01 Investment balance Assets:Brokerage 50 AAPL 50 AAPL + Equity:Opening Balan.. $-1,500.00 $-1,500.00 + 50 AAPL + 2004/05/14 Pay day Assets:Bank:Checking $500.00 $-1,000.00 + 50 AAPL + Income:Salary $-500.00 $-1,500.00 + 50 AAPL + 2004/05/27 Book Store Expenses:Books $20.00 $-1,480.00 + 50 AAPL + Liabilities:MasterCard $-20.00 $-1,500.00 + 50 AAPL + (Liabilities:Taxes) $-2.00 $-1,502.00 + 50 AAPL + 2004/05/27 Credit card company Liabilities:MasterCard $20.00 $-1,482.00 + 50 AAPL + Assets:Bank:Checking $-20.00 $-1,502.00 + 50 AAPL + + This rather verbose output shows every account transaction in +`sample.dat', and how it affects the running total. The final total is +identical to what we saw with the plain `balance' command. To see how +things really balance, we can use `--real -B', just as we did with +`balance': + + ledger --real -B reg + + Reports: + + 2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 + Equity:Opening Balan.. $-1,000.00 0 + 2004/05/01 Investment balance Assets:Brokerage $1,500.00 $1,500.00 + Equity:Opening Balan.. $-1,500.00 0 + 2004/05/14 Pay day Assets:Bank:Checking $500.00 $500.00 + Income:Salary $-500.00 0 + 2004/05/27 Book Store Expenses:Books $20.00 $20.00 + Liabilities:MasterCard $-20.00 0 + 2004/05/27 Credit card company Liabilities:MasterCard $20.00 $20.00 + Assets:Bank:Checking $-20.00 0 + + Here we see that everything balances to zero in the end, as it must. + +2.1.2.1 Specific register queries +................................. + +The most common use of the register command is to summarize +transactions based on the account(s) they affect. Using `sample.dat' +as as example, we could look at all book purchases using: + + ledger reg books + + Reports: + + 2004/05/29 Book Store Expenses:Books $20.00 $20.00 + + If a double-dash (`--') occurs in the list of regular expressions, +any following arguments are matched against payee names, instead of +account names: + + ledger reg ^liab -- credit + + Reports: + + 2004/05/29 Credit card company Liabilities:MasterCard $20.00 $20.00 + + There are many reporting options for tailoring which transactions are +found, and also how to summarize the various amounts and totals that +result. These are plumbed in greater depth below. + +2.1.3 Selecting transactions +---------------------------- + +Although the easiest way to use the register is to report all the +transactions affecting a set of accounts, it can often result in more +information than you want. To cope with an ever-growing amount of +data, there are several options which can help you pinpoint your report +to exactly the transactions that interest you most. This is called the +"calculation" phase of Ledger. All of its related options are +documented under `--help-calc'. + +2.1.3.1 By date +............... + +`--current'(`-c') displays entries occurring on or before the current +date. Any entry recorded for a future date will be ignored, as if it +had not been seen. This is useful if you happen to pre-record entries, +but still wish to view your balances in terms of what is available +today. + + `--begin DATE' (`-b DATE') limits the report to only those entries +occurring on or after DATE. The running total in the register will +start at zero with the first transaction, even if there are earlier +entries. + + To limit the display only, but still add earlier transactions to the +running total, use the display expression `-d 'd>=[DATE]''): + + ledger --basis -b may -d 'd>=[5/14]' reg ^assets + + Reports: + + 2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 + 2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 + + In this example, the displayed transactions start from `5/14', but +the calculated total starts from the beginning of `may'. + + `--end DATE' (`-e DATE') states when reporting should end, both +calculation and display. The ending date is inclusive. + + The DATE argument to the `-b' and `-e' options can be rather +flexible. Assuming the current date to be November 15, 2004, then all +of the following are equivalent: + + ledger -b oct bal + ledger -b "this oct" bal + ledger -b 2004/10 bal + ledger -b 10 bal + ledger -b last bal + ledger -b "last month" bal + + To constrain the report to a specific time period, use `--period' +(`-p'). A time period may have both a beginning and an end, or +neither, as well as a specified interval. Here are a few examples: + + ledger -p 2004 bal + ledger -p august bal + ledger -p "from aug to oct" bal + ledger -p "daily from 8/1 to 8/15" bal + ledger -p "weekly since august" bal + ledger -p "monthly from feb to oct" bal + ledger -p "quarterly in 2004" bal + ledger -p yearly bal + + See *Note Period expressions:: for more on syntax. Also, all of the +options `-b', `-e' and `-p' may be used together, but whatever +information occurs last takes priority. An example of such usage (in a +script, perhaps) would be: + + ledger -b 2004 -e 2005 -p monthly reg ^expenses + + This command is identical to: + + ledger -p "monthly in 2004" reg ^expenses + + The transactions within a period may be sorted using +`--period-sort', which takes a value expression. This is similar to +the `--sort' option, except that it sorts within each period entry, +rather than sorting all transactions in the report. See the +documentation on `--sort' below for more details. + +2.1.3.2 By status +................. + +By default, all regular transactions are included in each report. To +limit the report to certain kinds of transactions, use one or more of +the following options: + +`-C, --cleared' + Consider only cleared transactions. + +`-U, --uncleared' + Consider only uncleared and pending transactions. + +`-R, --real' + Consider only real (non-virtual) transactions. + +`-L, --actual' + Consider only actual (non-automated) transactions. + + Cleared transactions are indicated by an asterix placed just before +the payee name in a transaction. The meaning of this flag is up to the +user, but typically it means that an entry has been seen on a financial +statement. Pending transactions use an exclamation mark in the same +position, but are mainly used only by reconciling software. Uncleared +transactions are for things like uncashed checks, credit charges that +haven't appeared on a statement yet, etc. + + Real transactions are all non-virtual transactions, where the account +name is not surrounded by parentheses or square brackets. Virtual +transactions are useful for showing a transfer of money that never +really happened, like money set aside for savings without actually +transferring it from the parent account. + + Actual transactions are those not generated, either as part of an +automated entry, or a budget or forecast report. A useful of when you +might like to filter out generated transactions is with a budget: + + ledger --budget --actual reg ^expenses + + This command outputs all transactions affecting a budgeted account, +but without subtracting the budget amount (because the generated +transactions are suppressed with `--actual'). The report shows how +much you actually spent on budgeted items. + +2.1.3.3 By relationship +....................... + +Normally, a register report includes only the transactions that match +the regular expressions specified after the command word. For example, +to report all expenses: + + ledger reg ^expenses + + This reports: + + 2004/05/29 Book Store Expenses:Books $20.00 $20.00 + + Using `--related' (`-r') reports the transactions that did not match +your query, but only in entries that otherwise would have matched. +This has the effect of indicating where money came from, or when to: + + ledger -r reg ^expenses + + Reports: + + 2004/05/29 Book Store Liabilities:MasterCard $20.00 $20.00 + +2.1.3.4 By budget +................. + +There is more information about budgeting and forecasting in *Note +Budgeting and forecasting::. Basically, if you have any period entries +in your ledger file, you can use these options. A period entry looks +like: + + ~ Monthly + Assets:Bank:Checking $500.00 + Income:Salary + + The difference from a regular entry is that the first line begins +with a tilde (~), and instead of a payee there's a period expression +(*Note Period expressions::). Otherwise, a period entry is in every +other way the same as a regular entry. + + With such an entry in your ledger file, the `--budget' option will +report only transactions that match a budgeted account. Using +`sample.dat' from above: + + ledger --budget reg ^income + + Reports: + + 2004/05/01 Budget entry Income:Salary $500.00 $500.00 + 2004/05/14 Pay day Income:Salary $-500.00 0 + + The final total is zero, indicating that the budget matched exactly +for the reported period. Budgeting is most often helpful with period +reporting; for example, to show monthly budget results use `--budget -p +monthly'. + + The `--add-budget' option reports all matching transactions in +addition to budget transactions; while `--unbudgeted' shows only those +that don't match a budgeted account. To summarize: + +`--budget' + Show transactions matching budgeted accounts. + +`--unbudgeted' + Show transactions matching unbudgeted accounts. + +`--add-budget' + Show both budgeted and unbudgeted transactions together (i.e., add + the generated budget transactions to the regular report). + + A report with the `--forecast' option will add budgeted transactions +while the specified value expression is true. For example: + + ledger --forecast 'd<[2005] reg ^income + + Reports: + + 2004/05/14 Pay day Income:Salary $-500.00 $-500.00 + 2004/12/01 Forecast entry Income:Salary $-500.00 $-1,000.00 + 2005/01/01 Forecast entry Income:Salary $-500.00 $-1,500.00 + + The date this report was made was November 5, 2004; the reason the +first forecast entry is in december is that forecast entries are only +added for the future, and they only stop after the value expression has +matched at least once, which is why the January entry appears. A +forecast report can be very useful for determining when money will run +out in an account, or for projecting future cash flow: + + ledger --forecast 'd<[2008]' -p yearly reg ^inc ^exp + + This reports balances projected income against projected expenses, +showing the resulting total in yearly intervals until 2008. For the +case of `sample.dat', which has no budgeted expenses, the result of the +above command (in November 2004) is: + + 2004/01/01 - 2004/12/31 Income:Salary $-1,000.00 $-1,000.00 + Expenses:Books $20.00 $-980.00 + 2005/01/01 - 2005/12/31 Income:Salary $-6,000.00 $-6,980.00 + 2006/01/01 - 2006/12/31 Income:Salary $-6,000.00 $-12,980.00 + 2007/01/01 - 2007/12/31 Income:Salary $-6,000.00 $-18,980.00 + 2008/01/01 - 2008/01/01 Income:Salary $-500.00 $-19,480.00 + +2.1.3.5 By value expression +........................... + +Value expressions can be quite complex, and are treated more fully in +*Note Value expressions::. They can be used for limiting a report with +`--limit' (`-l'). The following command report income since august, +but expenses since october: + + ledger -l '(/income/&d>=[aug])|(/expenses/&d>=[oct])' reg + + The basic form of this value expression is `(A&B)|(A&B)'. The `A' +in each part matches against an account name with `/name/', while each +`B' part compares the date of the transaction (`d') with a specified +month. The resulting report will contain only transactions which match +the value expression. + + Another use of value expressions is to calculate the amount reported +for each line of a register report, or for computing the subtotal of +each account shown in a balance report. This example divides each +transaction amount by two: + + ledger -t 'a/2' reg ^exp + + The `-t' option doesn't affect the running total, only how the +transaction amount is displayed. To change the running total, use +`-T'. In that case, you will likely want to use the total (`O') +instead of the amount (`a'): + + ledger -T 'O/2' reg ^exp + +2.1.4 Massaging register output +------------------------------- + +Even after filtering down your data to just the transactions you're +interested in, the default reporting method of one transaction per line +is often still too much. To combat this complexity, it is possible to +ask Ledger to report the details to you in many different forms, +summarized in various ways. This is the "display" phase of Ledger, and +is documented under `--help-disp'. + +2.1.4.1 Summarizing +................... + +When multiple transactions relate to a single entry, they are reported +as part of that entry. For example, in the case of `sample.dat': + + ledger reg -- book + + Reports: + + 2004/05/29 Book Store Expenses:Books $20.00 $20.00 + Liabilities:MasterCard $-20.00 0 + (Liabilities:Taxes) $-2.00 $-2.00 + + All three transactions are part of one entry, and as such the entry +details are printed only once. To report every entry on a single line, +use `-n' to collapse entries with multiple transactions: + + ledger -n reg -- book + + Reports: + + 2004/05/29 Book Store $-2.00 $-2.00 + + In the balance report, `-n' causes the grand total not to be +displayed at the bottom of the report. + + If an account occurs more than once in a report, it is possible to +combine them all and report the total per-account, using `-s'. For +example, this command: + + ledger -B reg ^assets + + Reports: + + 2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 + 2004/05/01 Investment balance Assets:Brokerage $1,500.00 $2,500.00 + 2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 + 2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 + + But if the `-s' option is added, the result becomes: + + 2004/05/01 - 2004/05/29 Assets:Bank:Checking $1,480.00 $1,480.00 + Assets:Brokerage $1,500.00 $2,980.00 + + When account subtotaling is used, only one entry is printed, and the +date and name reflect the range of the combined transactions. + + With `-P', transactions relating to the same payee are combined. In +this case, the date of the combined entry is that of the latest +transaction. + + `-x' changes the payee name for each transaction to be the same as +the commodity it uses. This can be especially useful combined with +other options, like `-P'. For example: + + ledger -Px reg ^assets + + Reports: + + 2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 + 2004/05/01 AAPL Assets:Brokerage 50 AAPL $1,480.00 + 50 AAPL + + This reports shows the subtotal for each commodity held, and where it +is located. To see the basis cost, or initial investment, add `-B'. +Applied to the example above: + + 2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 + 2004/05/01 AAPL Assets:Brokerage $1,500.00 $2,980.00 + + The only other options which affect summarized totals is `-E', which +works only in the balance report. In this case, it shows matching +accounts with a zero a balance, which are ordinarily excluded. This +can be useful to see all the accounts involved in a report, even if +some have no total. + +2.1.4.2 Quick periods +..................... + +Although the `-p' option (also `--period') is much more versatile, +there are other options to make the most common period reports easier: + +`-W, --weekly' + Show weekly sub-totals. Same as `-p weekly'. + +`-M, --monthly' + Show monthly sub-totals. Same as `-p monthly'. + +`-Y, --yearly' + Show yearly sub-totals. Same as `-p yearly'. + + There is one kind of period report cannot be done with `-p'. This +is the `--dow', or "days of the week" report, which shows summarized +totals for each day of the week. The following examples shows a "day +of the week" report of income and expenses: + + ledger --dow reg ^inc ^exp + + Reports: + + 2004/05/27 Thursdays Expenses:Books $20.00 $20.00 + 2004/05/14 Fridays Income:Salary $-500.00 $-480.00 + +2.1.4.3 Ordering and width +.......................... + +The transactions displayed in a report are shown in the same order as +they appear in the ledger file. To change the order and sort a report, +use the `--sort' option. `--sort' takes a value expression to +determine the value to sort against, making it possible to sort +according to complex criteria. Here are some simple and useful +examples: + + ledger --sort d reg ^exp # sort by date + ledger --sort t reg ^exp # sort by amount total + ledger --sort -t reg ^exp # reverse sort by amount total + ledger --sort Ut reg ^exp # sort by abs amount total + + For the balance report, you will want to use `T' instead of `t': + + ledger --sort T reg ^exp # sort by amount total + ledger --sort -T reg ^exp # reverse sort by amount total + ledger --sort UT reg ^exp # sort by abs amount total + + The `--sort' options sorts all transactions in a report. If periods +are used (such as `--monthly'), this can get somewhat confusing. In +that case, you'll probably want to sort within periods using +`--period-sort' instead of `--sort'. + + And if the register seems too cramped, and you have a lot of screen +real estate, you can use `-w' to format the report within 132 acolumns, +instead of 80. You are more likely then to see full payee and account +names, as well as properly formatted totals when long-named commodities +are used. + + If you want only the first or last N entries to be printed--which can +be very useful for viewing the last 10 entries in your checking +account, while also showing the cumulative balance from all +entries--use the `--head' and/or `--tail' options. The two options may +be used simultaneously, for example: + + ledger --tail 20 reg checking + + If the output from your command is very long, Ledger can output the +data to a pager utility, such as `more' or `less': + + ledger --pager /usr/bin/less reg checking + +2.1.4.4 Averages and percentages +................................ + +To see the running total changed to a running average, use `-A'. The +final transaction's total will be the overall average of all displayed +transactions. The works in conjunction with period reporting, so that +you can see your monthly average expenses with: + + ledger -AM reg ^expenses:food + ledger -AMn reg ^expenses + + This works in the balance report too: + + ledger -AM bal ^expenses:food + ledger -AMs bal ^expenses + + The `-D' option changes the running average into a deviation from +the running average. This only makes sense in the register report, +however. + + ledger -DM reg ^expenses:food + + In the balance report only, `-%' changes the reported totals into a +percentage of the parent account. This kind of report is confusing if +negative amounts are involved, and doesn't work at all if multiple +commodities occur in an account's history. It has a somewhat limited +usefulness, therefore, but in certain cases it can be handy, such as +reviewing overall expenses: + + ledger -%s -S T bal ^expenses + +2.1.4.5 Reporting total data +............................ + +Normally in the `xml' report, only transaction amounts are printed. To +include the running total under a `' tag, use `--totals'. This +does not affect any other report. + + In the register report only, the output can be changed with `-j' to +show only the date and the amount--without commodities. This only +makes sense if a single commodity appears in the report, but can be +quite useful for scripting, or passing the data to Gnuplot. To show +only the date and running total, use `-J'. + +2.1.4.6 Display by value expression +................................... + +With `-d' you can decide which transactions (or accounts in the balance +report) are displayed, according to a value expression. The computed +total is not affected, only the display. This can be very useful for +shortening a report without changing the running total: + + ledger -d 'd>=[last month]' reg checking + + This command shows the checking account's register, beginning from +last month, but with the running total reflecting the entire history of +the account. + +2.1.4.7 Change report format +............................ + +When dates are printed in any report, the default format is `%Y/%m/%d', +which yields dates of the form `YYYY/mm/dd'. This can be changed with +`-y', whose argument is a `strftime' string--see your system's C +library documentation for the allowable codes. Mostly you will want to +use `%Y', `%m' and `%d', in whatever combination is convenient for your +locale. + + To change the format of the entire reported line, use `-F'. It +supports quite a large number of options, which are all documented in +*Note Format strings::. In addition, each specific kind of report +(except for `xml') can be changed using one of the following options: + +`--balance-format' + `balance' report. Default: + %20T %2_%-a\n + +`--register-format' + `register' report. Default: + %D %-.20P %-.22A %12.66t %12.80T\n%/%32|%-.22A %12.66t %12.80T\n + +`--print-format' + `print' report. Default: + %D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n + +`--plot-amount-format' + `register' report when `-j' (plot amount) is used. Default: + %D %(St)\n + +`--plot-total-format' + `register' report when `-J' (plot total) is used. Default: + %D %(ST)\n + +`--equity-format' + `equity' report. Default: + \n%D %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n + +`--prices-format' + `prices' report. Default: + \n%D %Y%C%P\n%/ %-34W %12t\n + +`--wide-register-format' + `register' report when `-w' (wide) is used. Default: + %D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n + +2.1.5 Standard queries +---------------------- + +If your ledger file uses the standard top-level accounts: Assets, +Liabilities, Income, Expenses, Equity: then the following queries will +enable you to generate some typical accounting reports from your data. + + Your _net worth_ can be determined by balancing assets against +liabilities: + + ledger bal ^assets ^liab + + By removing long-term investment and loan accounts, you can see your +current net liquidity (or liquid net worth): + + ledger bal ^assets ^liab -retirement -brokerage -loan + + Balancing expenses against income yields your _cash flow_, or net +profit/loss: + + ledger bal ^exp ^inc + + In this case, if the number is positive it means you spent more than +you earned during the report period. + + The most often used command is the "balance" command: + + export LEDGER=/home/johnw/doc/ledger.dat + ledger balance + + Here I've set my Ledger environment variable to point to where my +ledger file is hiding. Thereafter, I needn't specify it again. + +2.1.6 Reporting balance totals +------------------------------ + +The balance command prints out the summarized balances of all my +top-level accounts, excluding sub-accounts. In order to see the +balances for a specific account, just specify a regular expression +after the balance command: + + ledger balance expenses:food + + This will show all the money that's been spent on food, since the +beginning of the ledger. For food spending just this month +(September), use: + + ledger -p sep balance expenses:food + + Or maybe you want to see all of your assets, in which case the -s +(show sub-accounts) option comes in handy: + + ledger -s balance ^assets + + To exclude a particular account, use a regular expression with a +leading minus sign. The following will show all expenses, but without +food spending: + + ledger balance expenses -food + +2.1.7 Reporting percentages +--------------------------- + +There is no built-in way to report transaction amounts or account +balances in terms of percentages + + ---------- Footnotes ---------- + + (1) It is impossible for accounts not to balance in ledger; it +reports an error if a transaction does not balance + + (2) If it ever does, then generated transactions are involved, which +can be removed using `--actual' + + +File: ledger.info, Node: Commands, Next: Options, Prev: Usage overview, Up: Running Ledger + +2.2 Commands +============ + +2.2.1 balance +------------- + +The `balance' command reports the current balance of all accounts. It +accepts a list of optional regexps, which confine the balance report to +the matching accounts. If an account contains multiple types of +commodities, each commodity's total is reported separately. + +2.2.2 register +-------------- + +The `register' command displays all the transactions occurring in a +single account, line by line. The account regexp must be specified as +the only argument to this command. If any regexps occur after the +required account name, the register will contain only those +transactions that match. Very useful for hunting down a particular +transaction. + + The output from `register' is very close to what a typical +checkbook, or single-account ledger, would look like. It also shows a +running balance. The final running balance of any register should +always be the same as the current balance of that account. + + If you have Gnuplot installed, you may plot the amount or running +total of any register by using the script `report', which is included +in the Ledger distribution. The only requirement is that you add +either `-j' or `-J' to your register command, in order to plot either +the amount or total column, respectively. + +2.2.3 print +----------- + +The `print' command prints out ledger entries in a textual format that +can be parsed by Ledger. They will be properly formatted, and output +in the most economic form possible. The "print" command also takes a +list of optional regexps, which will cause only those transactions +which match in some way to be printed. + + The `print' command can be a handy way to clean up a ledger file +whose formatting has gotten out of hand. + +2.2.4 output +------------ + +The `output' command is very similar to the `print' command, except +that it attempts to replicate the specified ledger file exactly. The +format of the command is: + + ledger -f FILENAME output FILENAME + + Where `FILENAME' is the name of the ledger file to output. The +reason for specifying this command is that only entries contained +within that file will be output, and not an included entries (as can +happen with the `print' command). + +2.2.5 xml +--------- + +The `xml' command outputs results similar to what `print' and +`register' display, but as an XML form. This data can then be read in +and processed. Use the `--totals' option to include the running total +with each transaction. + +2.2.6 emacs +----------- + +The `emacs' command outputs results in a form that can be read directly +by Emacs Lisp. The format of the sexp is: + + ((BEG-POS CLEARED DATE CODE PAYEE + (ACCOUNT AMOUNT)...) ; list of transactions + ...) ; list of entries + +2.2.7 equity +------------ + +The `equity' command prints out accounts balances as if they were +entries. This makes it easy to establish the starting balances for an +account, such as when *Note Archiving previous years::. + +2.2.8 prices +------------ + +The `prices' command displays the price history for matching +commodities. The `-A' flag is useful with this report, to display the +running average price, or `-D' to show each price's deviation from that +average. + + There is also a `pricesdb' command which outputs the same +information as `prices', but does in a format that can be parsed by +Ledger. + +2.2.9 entry +----------- + +The `entry' commands simplifies the creation of new entries. It works +on the principle that 80% of all transactions are variants of earlier +transactions. Here's how it works: + + Say you currently have this transaction in your ledger file: + + 2004/03/15 * Viva Italiano + Expenses:Food $12.45 + Expenses:Tips $2.55 + Liabilities:MasterCard $-15.00 + + Now it's `2004/4/9', and you've just eating at `Viva Italiano' +again. The exact amounts are different, but the overall form is the +same. With the `entry' command you can type: + + ledger entry 2004/4/9 viva food 11 tips 2.50 + + This produces the following output: + + 2004/04/09 Viva Italiano + Expenses:Food $11.00 + Expenses:Tips $2.50 + Liabilities:MasterCard $-13.50 + + It works by finding a past transaction matching the regular +expression `viva', and assuming that any accounts or amounts specified +will be similar to that earlier transaction. If Ledger does not +succeed in generating a new entry, an error is printed and the exit +code is set to `1'. + + There is a shell script in the distribution's `scripts' directory +called `entry', which simplifies the task of adding a new entry to your +ledger. It launches `vi' to confirm that the entry looks appropriate. + + Here are a few more examples of the `entry' command, assuming the +above journal entry: + + ledger entry 4/9 viva 11.50 + ledger entry 4/9 viva 11.50 checking # (from `checking') + ledger entry 4/9 viva food 11.50 tips 8 + ledger entry 4/9 viva food 11.50 tips 8 cash + ledger entry 4/9 viva food $11.50 tips $8 cash + ledger entry 4/9 viva dining "DM 11.50" + + +File: ledger.info, Node: Options, Next: Format strings, Prev: Commands, Up: Running Ledger + +2.3 Options +=========== + +With all of the reports, command-line options are useful to modify the +output generated. These command-line options always occur before the +command word. This is done to distinguish options from exclusive +regular expressions, which also begin with a dash. The basic form for +most commands is: + + ledger [OPTIONS] COMMAND [REGEXPS...] [-- [REGEXPS...]] + + The OPTIONS and REGEXPS expressions are both optional. You could +just use `ledger balance', without any options--which prints a summary +of all accounts. But for more specific reporting, or to change the +appearance of the output, options are needed. + +* Menu: + +* Basic options:: +* Report filtering:: +* Output customization:: +* Commodity reporting:: +* Environment variables:: + + +File: ledger.info, Node: Basic options, Next: Report filtering, Prev: Options, Up: Options + +2.3.1 Basic options +------------------- + +These are the most basic command options. Most likely, the user will +want to set them using *Note Environment variables::, instead of using +actual command-line options: + + `--help' (`-h') prints a summary of all the options, and what they +are used for. This can be a handy way to remember which options do +what. This help screen is also printed if ledger is run without a +command. + + `--version' (`-v') prints the current version of ledger and exits. +This is useful for sending bug reports, to let the author know which +version of ledger you are using. + + `--file FILE' (`-f FILE') reads FILE as a ledger file. This command +may be used multiple times. FILE may also be a list of file names +separated by colons. Typically, the environment variable `LEDGER_FILE' +is set, rather than using this command-line option. + + `--output FILE' (`-o FILE') redirects output from any command to +FILE. By default, all output goes to standard output. + + `--init-file FILE' (`-i FILE') causes FILE to be read by ledger +before any other ledger file. This file may not contain any +transactions, but it may contain option settings. To specify options +in the init file, use the same syntax as the command-line. Here's an +example init file: + + --price-db ~/finance/.pricedb + + ; ~/.ledgerrc ends here + + Option settings on the command-line or in the environment always take +precedence over settings in the init file. + + `--cache FILE' identifies FILE as the default binary cache file. +That is, if the ledger files to be read are specified using the +environment variable `LEDGER_FILE', then whenever a command is finished +a binary copy will be written to the specified cache, to speed up the +loading time of subsequent queries. This filename can also be given +using the environment variable `LEDGER_CACHE', or by putting the option +into your init file. The `--no-cache' option causes Ledger to always +ignore the binary cache. + + `--account NAME' (`-a NAME') specifies the default account which QIF +file transactions are assumed to relate to. + + +File: ledger.info, Node: Report filtering, Next: Output customization, Prev: Basic options, Up: Options + +2.3.2 Report filtering +---------------------- + +These options change which transactions affect the outcome of a report, +in ways other than just using regular expressions: + + `--current'(`-c') displays only entries occurring on or before the +current date. + + `--begin DATE' (`-b DATE') constrains the report to entries on or +after DATE. Only entries after that date will be calculated, which +means that the running total in the balance report will always start at +zero with the first matching entry. (Note: This is different from +using `--display' to constrain what is displayed). + + `--end DATE' (`-e DATE') constrains the report so that entries on or +after DATE are not considered. The ending date is inclusive. + + `--period STR' (`-p STR') sets the reporting period to STR. This +will subtotal all matching entries within each period separately, +making it easy to see weekly, monthly, quarterly, etc., transaction +totals. A period string can even specify the beginning and end of the +report range, using simple terms like "last june" or "next month". For +more using period expressions, see *Note Period expressions::. + + `--period-sort EXPR' sorts the transactions within each reporting +period using the value expression EXPR. This is most often useful when +reporting monthly expenses, in order to view the highest expense +categories at the top of each month: + + ledger -M --period-sort -At reg ^Expenses + + `--cleared' (`-C') displays only transactions whose entry has been +marked "cleared" (by placing an asterix to the right of the date). + + `--uncleared' (`-U') displays only transactions whose entry has not +been marked "cleared" (i.e., if there is no asterix to the right of the +date). + + `--real' (`-R') displays only real transactions, not virtual. (A +virtual transaction is indicated by surrounding the account name with +parentheses or brackets; see the section on using virtual transactions +for more information). + + `--actual' (`-L') displays only actual transactions, and not those +created due to automated transactions. + + `--related' (`-r') displays transactions that are related to +whichever transactions would otherwise have matched the filtering +criteria. In the register report, this shows where money went to, or +the account it came from. In the balance report, it shows all the +accounts affected by entries having a related transaction. For +example, if a file had this entry: + + 2004/03/20 Safeway + Expenses:Food $65.00 + Expenses:Cash $20.00 + Assets:Checking $-85.00 + + And the register command was: + + ledger -r register food + + The following would be output, showing the transactions related to +the transaction that matched: + + 2004/03/20 Safeway Expenses:Cash $-20.00 $-20.00 + Assets:Checking $85.00 $65.00 + + `--budget' is useful for displaying how close your transactions meet +your budget. `--add-budget' also shows unbudgeted transactions, while +`--unbudgeted' shows only those. `--forecast' is a related option that +projects your budget into the future, showing how it will affect future +balances. *Note Budgeting and forecasting::. + + `--limit EXPR' (`-l EXPR') limits which transactions take part in +the calculations of a report. + + `--amount EXPR' (`-t EXPR') changes the value expression used to +calculate the "value" column in the `register' report, the amount used +to calculate account totals in the `balance' report, and the values +printed in the `equity' report. *Note Value expressions::. + + `--total EXPR' (`-T EXPR') sets the value expression used for the +"totals" column in the `register' and `balance' reports. + + +File: ledger.info, Node: Output customization, Next: Commodity reporting, Prev: Report filtering, Up: Options + +2.3.3 Output customization +-------------------------- + +These options affect only the output, but not which transactions are +used to create it: + + `--collapse' (`-n') causes entries in a `register' report with +multiple transactions to be collapsed into a single, subtotaled entry. + + `--subtotal' (`-s') causes all entries in a `register' report to be +collapsed into a single, subtotaled entry. + + `--by-payee' (`-P') reports subtotals by payee. + + `--comm-as-payee' (`-x') changes the payee of every transaction to +be the commodity used in that transaction. This can be useful when +combined with other options, such as `-s'. + + `--empty' (`-E') includes even empty accounts in the `balance' +report. + + `--weekly' (`-W') reports transaction totals by the week. The week +begins on whichever day of the week begins the month containing that +transaction. To set a specific begin date, use a period string, such +as `weekly from DATE'. `--monthly' (`-M') reports transaction totals +by month; `--yearly' (`-Y') reports transaction totals by year. For +more complex period, using the `--period' option described above. + + `--dow' reports transactions totals for each day of the week. This +is an easy way to see if weekend spending is more than on weekdays. + + `--sort EXPR' (`-S EXPR') sorts a report by comparing the values +determined using the value expression EXPR. For example, using `-S +-UT' in the balance report will sort account balances from greatest to +least, using the absolute value of the total. For more on how to use +value expressions, see *Note Value expressions::. + + `--wide' (`-w') causes the default `register' report to assume 132 +columns instead of 80. + + `--head' causes only the first N entries to be printed. This is +different from using the command-line utility `head', which would limit +to the first N transactions. `--tail' outputs only the last N entries. +Both options may be used simultaneously. If a negative amount is +given, it will invert the meaning of the flag (instead of the first +five entries being printed, for example, it would print all but the +first five). + + `--pager' tells Ledger to pass its output to the given pager +program--very useful when the output is especially long. This behavior +can be made the default by setting the `LEDGER_PAGER' environment +variable. + + `--average' (`-A') reports the average transaction value. + + `--deviation' (`-D') reports each transaction's deviation from the +average. It is only meaningful in the `register' and `prices' reports. + + `--percentage' (`-%') shows account subtotals in the `balance' +report as percentages of the parent account. + + `--totals' include running total information in the `xml' report. + + `--amount-data' (`-j') changes the `register' report so that it +output nothing but the date and the value column, and the latter +without commodities. This is only meaningful if the report uses a +single commodity. This data can then be fed to other programs, which +could plot the date, analyze it, etc. + + `--total-data' (`-J') changes the `register' report so that it +output nothing but the date and totals column, without commodities. + + `--display EXPR' (`-d EXPR') limits which transactions or accounts +or actually displayed in a report. They might still be calculated, and +be part of the running total of a register report, for example, but +they will not be displayed. This is useful for seeing last month's +checking transactions, against a running balance which includes all +transaction values: + + ledger -d "d>=[last month]" reg checking + + The output from this command is very different from the following, +whose running total includes only transactions from the last month +onward: + + ledger -p "last month" reg checking + + Which is more useful depends on what you're looking to know: the +total amount for the reporting range (`-p'), or simply a display +restricted to the reporting range (using `-d'). + + `--date-format STR' (`-y STR') changes the basic date format used by +reports. The default uses a date like 2004/08/01, which represents the +default date format of `%Y/%m/%d'. To change the way dates are printed +in general, the easiest way is to put `--date-format FORMAT' in the +Ledger initialization file `~/.ledgerrc' (or the file referred to by +`LEDGER_INIT'). + + `--format STR' (`-F STR') sets the reporting format for whatever +report ledger is about to make. *Note Format strings::. There are +also specific format commands for each report type: + + * `--balance-format STR' + + * `--register-format STR' + + * `--print-format STR' + + * `--plot-amount-format STR' (-j `register') + + * `--plot-total-format STR' (-J `register') + + * `--equity-format STR' + + * `--prices-format STR' + + * `--wide-register-format STR' (-w `register') + + +File: ledger.info, Node: Commodity reporting, Next: Environment variables, Prev: Output customization, Up: Options + +2.3.4 Commodity reporting +------------------------- + +These options affect how commodity values are displayed: + + `--price-db FILE' sets the file that is used for recording +downloaded commodity prices. It is always read on startup, to +determine historical prices. Other settings can be placed in this file +manually, to prevent downloading quotes for a specific, for example. +This is done by adding a line like the following: + + ; Don't download quotes for the dollar, or timelog values + N $ + N h + + `--price-exp MINS' (`-L MINS') sets the expected freshness of price +quotes, in minutes. That is, if the last known quote for any commodity +is older than this value--and if `--download' is being used--then the +Internet will be consulted again for a newer price. Otherwise, the old +price is still considered to be fresh enough. + + `--download' (`-Q') causes quotes to be automagically downloaded, as +needed, by running a script named `getquote' and expecting that script +to return a value understood by ledger. A sample implementation of a +`getquote' script, implemented in Perl, is provided in the +distribution. Downloaded quote price are then appended to the price +database, usually specified using the environment variable +`LEDGER_PRICE_DB'. + + There are several different ways that ledger can report the totals it +displays. The most flexible way to adjust them is by using value +expressions, and the `-t' and `-T' options. However, there are also +several "default" reports, which will satisfy most users basic +reporting needs: + +`-O, --quantity' + Reports commodity totals (this is the default) + +`-B, --basis' + Reports the cost basis for all transactions. + +`-V, --market' + Reports the last known market value for all commodities. + +`-g, --performance' + Reports the net gain/loss for each transaction in a `register' + report. + +`-G --gain' + Reports the net gain/loss for all commodities in the report that + have a price history. + + +File: ledger.info, Node: Environment variables, Prev: Commodity reporting, Up: Options + +2.3.5 Environment variables +--------------------------- + +Every option to ledger may be set using an environment variable. If an +option has a long name such `--this-option', setting the environment +variable `LEDGER_THIS_OPTION' will have the same affect as specifying +that option on the command-line. Options on the command-line always +take precedence over environment variable settings, however. + + Note that you may also permanently specify option values by placing +option settings in the file `~/.ledgerrc', for example: + + --cache /tmp/.mycache + + +File: ledger.info, Node: Format strings, Next: Value expressions, Prev: Options, Up: Running Ledger + +2.4 Format strings +================== + +Format strings may be used to change the output format of reports. +They are specified by passing a formatting string to the `--format' +(`-F') option. Within that string, constructs are allowed which make +it possible to display the various parts of an account or transaction +in custom ways. + + Within a format strings, a substitution is specified using a percent +character (`%'). The basic format of all substitutions is: + + %[-][MIN WIDTH][.MAX WIDTH]EXPR + + If the optional minus sign (`-') follows the percent character, +whatever is substituted will be left justified. The default is right +justified. If a minimum width is given next, the substituted text will +be at least that wide, perhaps wider. If a period and a maximum width +is given, the substituted text will never be wider than this, and will +be truncated to fit. Here are some examples: + + %-P An entry's payee, left justified + %20P The same, right justified, at least 20 chars wide + %.20P The same, no more than 20 chars wide + %-.20P Left justified, maximum twenty chars wide + + The expression following the format constraints can be a single +letter, or an expression enclosed in parentheses or brackets. The +allowable expressions are: + +`%' + Inserts a percent sign. + +`t' + Inserts the results of the value expression specified by `-t'. If + `-t' was not specified, the current report style's value + expression is used. + +`T' + Inserts the results of the value expression specified by `-T'. If + `-T' was not specified, the current report style's value + expression is used. + +`|' + Inserts a single space. This is useful if a width is specified, + for inserting a certain number of spaces. + +`_' + Inserts a space for each level of an account's depth. That is, if + an account has two parents, this construct will insert two spaces. + If a minimum width is specified, that much space is inserted for + each level of depth. Thus `%5_', for an account with four + parents, will insert twenty spaces. + +`(EXPR)' + Inserts the amount resulting from the value expression given in + parentheses. To insert five times the total value of an account, + for example, one could say `%12(5*O)'. Note: It's important to put + the five first in that expression, so that the commodity doesn't + get stripped from the total. + +`[DATEFMT]' + Inserts the result of formatting a transaction's date with a date + format string, exactly like those supported by `strftime'. For + example: `%[%Y/%m/%d %H:%M:%S]'. + +`S' + Insert the pathname of the file from which the entry's data was + read. + +`B' + Inserts the beginning character position of that entry within the + file. + +`b' + Inserts the beginning line of that entry within the file. + +`E' + Inserts the ending character position of that entry within the + file. + +`e' + Inserts the ending line of that entry within the file. + +`D' + By default, this is the same as `%[%Y/%m%/d]'. The date format + used can be changed at any time with the `-y' flag, however. + Using `%D' gives the user more control over the way dates are + output. + +`d' + This is the same as the `%D' option, unless the entry has an + effective date, in which case it prints + `[ACTUAL_DATE=EFFECtIVE_DATE]'. + +`X' + If a transaction has been cleared, this inserts `*' followed by a + space; otherwise nothing is inserted. + +`Y' + This is the same as `%X', except that it only displays a state + character if all of the member transactions have the same state. + +`C' + Inserts the checking number for an entry, in parentheses, followed + by a space; if none was specified, nothing is inserted. + +`P' + Inserts the payee related to a transaction. + +`a' + Inserts the optimal short name for an account. This is normally + used in balance reports. It prints a parent account's name if + that name has not been printed yet, otherwise it just prints the + account's name. + +`A' + Inserts the full name of an account. + +`W' + This is the same as `%A', except that it first displays the + transaction's state _if the entry's transaction states are not all + the same_, followed by the full account name. This is offered as + a printing optimization, so that combined with `%Y', only the + minimum amount of state detail is printed. + +`o' + Inserts the "optimized" form of a transaction's amount. This is + used by the print report. In some cases, this inserts nothing; in + others, it inserts the transaction amount and its cost. It's use + is not recommend unless you are modifying the print report. + +`n' + Inserts the note associated with a transaction, preceded by two + spaces and a semi-colon, if it exists. Thus, no none becomes an + empty string, while the note `foo' is substituted as ` ; foo'. + +`N' + Inserts the note associated with a transaction, if one exists. + +`/' + The `%/' construct is special. It separates a format string + between what is printed for the first transaction of an entry, and + what is printed for all subsequent transactions. If not used, the + same format string is used for all transactions. + + +File: ledger.info, Node: Value expressions, Next: Period expressions, Prev: Format strings, Up: Running Ledger + +2.5 Value expressions +===================== + +Value expressions are an expression language used by Ledger to +calculate values used by the program for many different purposes: + + 1. The values displayed in reports + + 2. For predicates (where truth is anything non-zero), to determine + which transactions are calculated (`-l') or displayed (`-d'). + + 3. For sorting criteria, to yield the sort key. + + 4. In the matching criteria used by automated transactions. + + Value expressions support most simple math and logic operators, in +addition to a set of one letter functions and variables. A function's +argument is whatever follows it. The following is a display predicate +that I use with the `balance' command: + + ledger -d /^Liabilities/?T<0:UT>100 balance + + The effect is that account totals are displayed only if: 1) A +Liabilities account has a total less than zero; or 2) the absolute +value of the account's total exceeds 100 units of whatever commodity +contains. If it contains multiple commodities, only one of them must +exceed 100 units. + + Display predicates are also very handy with register reports, to +constrain which entries are printed. For example, the following +command shows only entries from the beginning of the current month, +while still calculating the running balance based on all entries: + + ledger -d "d>[this month]" register checking + + This advantage to this command's complexity is that it prints the +running total in terms of all entries in the register. The following, +simpler command is similar, but totals only the displayed transactions: + + ledger -b "this month" register checking + +2.5.1 Variables +--------------- + +Below are the one letter variables available in any value expression. +For the register and print commands, these variables relate to +individual transactions, and sometimes the account affected by a +transaction. For the balance command, these variables relate to +accounts--often with a subtle difference in meaning. The use of each +variable for both is specified. + +`t' + This maps to whatever the user specified with `-t'. In a register + report, `-t' changes the value column; in a balance report, it has + no meaning by default. If `-t' was not specified, the current + report style's value expression is used. + +`T' + This maps to whatever the user specified with `-T'. In a register + report, `-T' changes the totals column; in a balance report, this + is the value given for each account. If `-T' was not specified, + the current report style's value expression is used. + +`m' + This is always the present moment/date. + +2.5.1.1 Transaction/account details +................................... + +`d' + A transaction's date, as the number of seconds past the epoch. + This is always "today" for an account. + +`a' + The transaction's amount; the balance of an account, without + considering children. + +`b' + The cost of a transaction; the cost of an account, without its + children. + +`v' + The market value of a transaction, or an account without its + children. + +`g' + The net gain (market value minus cost basis), for a transaction or + an account without its children. It is the same as `v-b'. + +`l' + The depth ("level") of an account. If an account has one parent, + it's depth is one. + +`n' + The index of a transaction, or the count of transactions affecting + an account. + +`X' + 1 if a transaction's entry has been cleared, 0 otherwise. + +`R' + 1 if a transaction is not virtual, 0 otherwise. + +`Z' + 1 if a transaction is not automated, 0 otherwise. + +2.5.1.2 Calculated totals +......................... + +`O' + The total of all transactions seen so far, or the total of an + account and all its children. + +`N' + The total count of transactions affecting an account and all its + children. + +`B' + The total cost of all transactions seen so far; the total cost of + an account and all its children. + +`V' + The market value of all transactions seen so far, or of an account + and all its children. + +`G' + The total net gain (market value minus cost basis), for a series of + transactions, or an account and its children. It is the same as + `V-B'. + +2.5.2 Functions +--------------- + +The available one letter functions are: + +`-' + Negates the argument. + +`U' + The absolute (unsigned) value of the argument. + +`S' + Strips the commodity from the argument. + +`A' + The arithmetic mean of the argument; `Ax' is the same as `x/n'. + +`P' + The present market value of the argument. The syntax `P(x,d)' is + supported, which yields the market value at time `d'. If no date + is given, then the current moment is used. + +2.5.3 Operators +--------------- + +The binary and ternary operators, in order of precedence, are: + + 1. `* /' + + 2. `+ -' + + 3. `! < > =' + + 4. `& | ?:' + +2.5.4 Complex expressions +------------------------- + +More complicated expressions are possible using: + +`NUM' + A plain integer represents a commodity-less amount. + +`{AMOUNT}' + An amount in braces can be any kind of amount supported by ledger, + with or without a commodity. Use this for decimal values. + +`/REGEXP/' + +`W/REGEXP/' + A regular expression that matches against an account's full name. + If a transaction, this will match against the account affected by + the transaction. + +`//REGEXP/' + +`p/REGEXP/' + A regular expression that matches against an entry's payee name. + +`///REGEXP/' + +`w/REGEXP/' + A regular expression that matches against an account's base name. + If a transaction, this will match against the account affected by + the transaction. + +`c/REGEXP/' + A regular expression that matches against the entry code (the text + that occurs between parentheses before the payee name). + +`e/REGEXP/' + A regular expression that matches against a transaction's note, or + comment field. + +`(EXPR)' + A sub-expression is nested in parenthesis. This can be useful + passing more complicated arguments to functions, or for overriding + the natural precedence order of operators. + +`[DATE]' + Useful specifying a date in plain terms. For example, you could + say `[2004/06/01]'. + + +File: ledger.info, Node: Period expressions, Next: File format, Prev: Value expressions, Up: Running Ledger + +2.6 Period expressions +====================== + +A period expression indicates a span of time, or a reporting interval, +or both. The full syntax is: + + [INTERVAL] [BEGIN] [END] + + The optional INTERVAL part may be any one of: + + every day + every week + every monthly + every quarter + every year + every N days # N is any integer + every N weeks + every N months + every N quarters + every N years + daily + weekly + biweekly + monthly + bimonthly + quarterly + yearly + + After the interval, a begin time, end time, both or neither may be +specified. As for the begin time, it can be either of: + + from + since + + The end time can be either of: + + to + until + + Where SPEC can be any of: + + 2004 + 2004/10 + 2004/10/1 + 10/1 + october + oct + this week # or day, month, quarter, year + next week + last week + + The beginning and ending can be given at the same time, if it spans a +single period. In that case, just use SPEC by itself. In that case, +the period `oct', for example, will cover all the days in october. The +possible forms are: + + + in + + Here are a few examples of period expressions: + + monthly + monthly in 2004 + weekly from oct + weekly from last month + from sep to oct + from 10/1 to 10/5 + monthly until 2005 + from apr + until nov + last oct + weekly last august + + +File: ledger.info, Node: File format, Next: Some typical queries, Prev: Period expressions, Up: Running Ledger + +2.7 File format +=============== + +The ledger file format is quite simple, but also very flexible. It +supports many options, though typically the user can ignore most of +them. They are summarized below. + + The initial character of each line determines what the line means, +and how it should be interpreted. Allowable initial characters are: + +`NUMBER' + A line beginning with a number denotes an entry. It may be + followed by any number of lines, each beginning with whitespace, + to denote the entry's account transactions. The format of the + first line is: + + DATE[=EDATE] [*|!] [(CODE)] DESC + + If `*' appears after the date (with optional effective date), it + indicates the entry is "cleared", which can mean whatever the user + wants it t omean. If `!' appears after the date, it indicates d + the entry is "pending"; i.e., tentatively cleared from the user's + point of view, but not yet actually cleared. If a `CODE' appears + in parentheses, it may be used to indicate a check number, or the + type of the transaction. Following these is the payee, or a + description of the transaction. + + The format of each following transaction is: + + ACCOUNT AMOUNT [; NOTE] + + The `ACCOUNT' may be surrounded by parentheses if it is a virtual + transactions, or square brackets if it is a virtual transactions + that must balance. The `AMOUNT' can be followed by a per-unit + transaction cost, by specifying ` AMOUNT', or a complete + transaction cost with `@ AMOUNT'. Lastly, the `NOTE' may specify + an actual and/or effective date for the transaction by using the + syntax `[ACTUAL_DATE]' or `[=EFFECTIVE_DATE]' or + `[ACTUAL_DATE=EFFECtIVE_DATE]'. + +`=' + An automated entry. A value expression must appear after the equal + sign. + + After this initial line there should be a set of one or more + transactions, just as if it were normal entry. If the amounts of + the transactions have no commodity, they will be applied as + modifiers to whichever real transaction is matched by the value + expression. + +`~' + A period entry. A period expression must appear after the tilde. + + After this initial line there should be a set of one or more + transactions, just as if it were normal entry. + +`!' + A line beginning with an exclamation mark denotes a command + directive. It must be immediately followed by the command word. + The supported commands are: + + `!include' + Include the stated ledger file. + + `!account' + The account name is given is taken to be the parent of all + transactions that follow, until `!end' is seen. + + `!end' + Ends an account block. + +`;' + A line beginning with a colon indicates a comment, and is ignored. + +`Y' + If a line begins with a capital Y, it denotes the year used for all + subsequent entries that give a date without a year. The year + should appear immediately after the Y, for example: `Y2004'. This + is useful at the beginning of a file, to specify the year for that + file. If all entries specify a year, however, this command has no + effect. + +`P' + Specifies a historical price for a commodity. These are usually + found in a pricing history file (see the `-Q' option). The syntax + is: + P DATE SYMBOL PRICE + +`N SYMBOL' + Indicates that pricing information is to be ignored for a given + symbol, nor will quotes ever be downloaded for that symbol. Useful + with a home currency, such as the dollar ($). It is recommended + that these pricing options be set in the price database file, which + defaults to `~/.pricedb'. The syntax for this command is: + N SYMBOL + +`D AMOUNT' + Specifies the default commodity to use, by specifying an amount in + the expected format. The `entry' command will use this commodity + as the default when none other can be determined. This command + may be used multiple times, to set the default flags for different + commodities; whichever is seen last is used as the default + commodity. For example, to set US dollars as the default + commodity, while also setting the thousands flag and decimal flag + for that commodity, use: + D $1,000.00 + +`C AMOUNT1 = AMOUNT2' + Specifies a commodity conversion, where the first amount is given + to be equivalent to the second amount. The first amount should + use the decimal precision desired during reporting: + C 1.00 Kb = 1024 bytes + +`i, o, b, h' + These four relate to timeclock support, which permits ledger to + read timelog files. See the timeclock's documentation for more + info on the syntax of its timelog files. + + +File: ledger.info, Node: Some typical queries, Next: Budgeting and forecasting, Prev: File format, Up: Running Ledger + +2.8 Some typical queries +======================== + +A query such as the following shows all expenses since last October, +sorted by total: + + ledger -b "last oct" -s -S T bal ^expenses + + From left to right the options mean: Show entries since October, +2003; show all sub-accounts; sort by the absolute value of the total; +and report the balance for all expenses. + +2.8.1 Reporting monthly expenses +-------------------------------- + +The following query makes it easy to see monthly expenses, with each +month's expenses sorted by the amount: + + ledger -M --period-sort t reg ^expenses + + Now, you might wonder where the money came from to pay for these +things. To see that report, add `-r', which shows the "related +account" transactions: + + ledger -M --period-sort t -r reg ^expenses + + But maybe this prints too much information. You might just want to +see how much you're spending with your MasterCard. That kind of query +requires the use of a display predicate, since the transactions +calculated must match `^expenses', while the transactions displayed +must match `mastercard'. The command would be: + + ledger -M -r -d /mastercard/ reg ^expenses + + This query says: Report monthly subtotals; report the "related +account" transactions; display only related transactions whose account +matches `mastercard', and base the calculation on transactions matching +`^expenses'. + + This works just as well for report the overall total, too: + + ledger -s -r -d /mastercard/ reg ^expenses + + The `-s' option subtotals all transactions, just as `-M' subtotaled +by the month. The running total in both cases is off, however, since a +display expression is being used. + +2.8.2 Visualizing with Gnuplot +------------------------------ + +If you have `Gnuplot' installed, you can graph any of the above +register reports. The script to do this is included in the ledger +distribution, and is named `scripts/report'. Install `report' anywhere +along your `PATH', and then use `report' instead of `ledger' when doing +a register report. The only thing to keep in mind is that you must +specify `-j' or `-J' to indicate whether Gnuplot should plot the +amount, or the running total. For example, this command plots total +monthly expenses made on your MasterCard. + + report -j -M -r -d /mastercard/ reg ^expenses + + The `report' script is a very simple Bourne shell script, that +passes a set of scripted commands to Gnuplot. Feel free to modify the +script to your liking, since you may prefer histograms to line plots, +for example. + +2.8.2.1 Typical plots +..................... + +Here are some useful plots: + + report -j -M reg ^expenses # monthly expenses + report -J reg checking # checking account balance + report -J reg ^income ^expenses # cash flow report + + # net worth report, ignoring non-$ transactions + + report -J -l "Ua>={\$0.01}" reg ^assets ^liab + + # net worth report starting last February. the use of a display + # predicate (-d) is needed, otherwise the balance will start at + # zero, and thus the y-axis will not reflect the true balance + + report -J -l "Ua>={\$0.01}" -d "d>=[last feb]" reg ^assets ^liab + + The last report uses both a calculation predicate (`-l') and a +display predicate (`-d'). The calculation predicates limits the report +to transactions whose amount is greater than $1 (which can only happen +if the transaction amount is in dollars). The display predicate limits +the entries _displayed_ to just those since last February, even those +entries from before then will be computed as part of the balance. + + +File: ledger.info, Node: Budgeting and forecasting, Prev: Some typical queries, Up: Running Ledger + +2.9 Budgeting and forecasting +============================= + +2.9.1 Budgeting +--------------- + +Keeping a budget allows you to pay closer attention to your income and +expenses, by reporting how far your actual financial activity is from +your expectations. + + To start keeping a budget, put some period entries at the top of your +ledger file. A period entry is almost identical to a regular entry, +except that it begins with a tilde and has a period expression in place +of a payee. For example: + + ~ Monthly + Expenses:Rent $500.00 + Expenses:Food $450.00 + Expenses:Auto:Gas $120.00 + Expenses:Insurance $150.00 + Expenses:Phone $125.00 + Expenses:Utilities $100.00 + Expenses:Movies $50.00 + Expenses $200.00 ; all other expenses + Assets + + ~ Yearly + Expenses:Auto:Repair $500.00 + Assets + + These two period entries give the usual monthly expenses, as well as +one typical yearly expense. For help on finding out what your average +monthly expense is for any category, use a command like: + + ledger -p "this year" -MAs bal ^expenses + + The reported totals are the current year's average for each account. + + Once these period entries are defined, creating a budget report is as +easy as adding `--budget' to the command-line. For example, a typical +monthly expense report would be: + + ledger -M reg ^exp + + To see the same report balanced against your budget, use: + + ledger --budget -M reg ^exp + + A budget report includes only those accounts that appear in the +budget. To see all expenses balanced against the budget, use +`--add-budget'. You can even see only the unbudgeted expenses using +`--unbudgeted': + + ledger --unbudgeted -M reg ^exp + + You can also use these flags with the `balance' command. + +2.9.2 Forecasting +----------------- + +Sometimes it's useful to know what your finances will look like in the +future, such as determining when an account will reach zero. Ledger +makes this easy to do, using the same period entries as are used for +budgeting. An example forecast report can be generated with: + + ledger --forecast "T>{\$-500.00}" register ^assets ^liabilities + + This report continues outputting transactions until the running total +is greater than $-500.00. A final transaction is always output, to +show you what the total afterwards would be. + + Forecasting can also be used with the balance report, but by date +only, and not against the running total: + + ledger --forecast "d<[2010]" bal ^assets ^liabilities + + +File: ledger.info, Node: Keeping a ledger, Next: Using XML, Prev: Running Ledger, Up: Top + +3 Keeping a ledger +****************** + +The most important part of accounting is keeping a good ledger. If you +have a good ledger, tools can be written to work whatever +mathematically tricks you need to better understand your spending +patterns. Without a good ledger, no tool, however smart, can help you. + + The Ledger program aims at making ledger entry as simple as possible. +Since it is a command-line tool, it does not provide a user interface +for keeping a ledger. If you like, you may use GnuCash to maintain +your ledger, in which case the Ledger program will read GnuCash's data +files directly. In that case, read the GnuCash manual now, and skip to +the next chapter. + + If you are not using GnuCash, but a text editor to maintain your +ledger, read on. Ledger has been designed to make data entry as simple +as possible, by keeping the ledger format easy, and also by +automagically determining as much information as possible based on the +nature of your entries. + + For example, you do not need to tell Ledger about the accounts you +use. Any time Ledger sees a transaction involving an account it knows +nothing about, it will create it. If you use a commodity that is new +to Ledger, it will create that commodity, and determine its display +characteristics (placement of the symbol before or after the amount, +display precision, etc) based on how you used the commodity in the +transaction. + + Here is the Pacific Bell example from above, given as a Ledger +transaction: + + 9/29 (100) Pacific Bell + Expenses:Utilities:Phone $23.00 + Assets:Checking $-23.00 + + As you can see, it is very similar to what would be written on paper, +minus the computed balance totals, and adding in account names that +work better with Ledger's scheme of things. In fact, since Ledger is +smart about many things, you don't need to specify the balanced amount, +if it is the same as the first line: + + 9/29 (100) Pacific Bell + Expenses:Utilities:Phone $23.00 + Assets:Checking + + For this entry, Ledger will figure out that $-23.00 must come from +`Assets:Checking' in order to balance the entry. + +* Menu: + +* Stating where money goes:: +* Assets and Liabilities:: +* Commodities and Currencies:: +* Accounts and Inventories:: +* Understanding Equity:: +* Dealing with Petty Cash:: +* Working with multiple funds and accounts:: +* Archiving previous years:: +* Virtual transactions:: +* Automated transactions:: +* Using Emacs to Keep Your Ledger:: +* Using GnuCash to Keep Your Ledger:: +* Using timeclock to record billable time:: + + +File: ledger.info, Node: Stating where money goes, Next: Assets and Liabilities, Prev: Keeping a ledger, Up: Keeping a ledger + +3.1 Stating where money goes +============================ + +Accountants will talk of "credits" and "debits", but the meaning is +often different from the layman's understanding. To avoid confusion, +Ledger uses only subtractions and additions, although the underlying +intent is the same as standard accounting principles. + + Recall that every transaction will involve two or more accounts. +Money is transferred from one or more accounts to one or more other +accounts. To record the transaction, an amount is _subtracted_ from +the source accounts, and _added_ to the target accounts. + + In order to write a Ledger entry correctly, you must determine where +the money comes from and where it goes to. For example, when you are +paid a salary, you must add money to your bank account and also +subtract it from an income account: + + 9/29 My Employer + Assets:Checking $500.00 + Income:Salary $-500.00 + + Why is the Income a negative figure? When you look at the balance +totals for your ledger, you may be surprised to see that Expenses are a +positive figure, and Income is a negative figure. It may take some +getting used to, but to properly use a general ledger you must think in +terms of how money moves. Rather than Ledger "fixing" the minus signs, +let's understand why they are there. + + When you earn money, the money has to come from somewhere. Let's +call that somewhere "society". In order for society to give you an +income, you must take money away (withdraw) from society in order to +put it into (make a payment to) your bank. When you then spend that +money, it leaves your bank account (a withdrawal) and goes back to +society (a payment). This is why Income will appear negative--it +reflects the money you have drawn from society--and why Expenses will +be positive--it is the amount you've given back. These additions and +subtractions will always cancel each other out in the end, because you +don't have the ability to create new money: it must always come from +somewhere, and in the end must always leave. This is the beginning of +economy, after which the explanation gets terribly difficult. + + Based on that explanation, here's another way to look at your balance +report: every negative figure means that that account or person or +place has less money now than when you started your ledger; and every +positive figure means that that account or person or place has more +money now that when you started your ledger. Make sense? + + +File: ledger.info, Node: Assets and Liabilities, Next: Commodities and Currencies, Prev: Stating where money goes, Up: Keeping a ledger + +3.2 Assets and Liabilities +========================== + +Assets are money that you have, and Liabilities are money that you owe. +"Liabilities" is just a more inclusive name for Debts. + + An Asset is typically increased by transferring money from an Income +account, such as when you get paid. Here is a typical entry: + + 2004/09/29 My Employer + Assets:Checking $500.00 + Income:Salary + + Money, here, comes from an Income account belonging to "My +Employer", and is transferred to your checking account. The money is +now yours, which makes it an Asset. + + Liabilities track money owed to others. This can happen when you +borrow money to buy something, or if you owe someone money. Here is an +example of increasing a MasterCard liability by spending money with it: + + 2004/09/30 Restaurant + Expenses:Dining $25.00 + Liabilities:MasterCard + + The Dining account balance now shows $25 spent on Dining, and a +corresponding $25 owed on the MasterCard--and therefore shown as +$-25.00. The MasterCard liability shows up as negative because it +offsets the value of your assets. + + The combined total of your Assets and Liabilities is your net worth. +So to see your current net worth, use this command: + + ledger balance ^assets ^liabilities + + Relatedly, your Income accounts show up negative, because they +transfer money _from_ an account in order to increase your assets. +Your Expenses show up positive because that is where the money went to. +The combined total of Income and Expenses is your cash flow. A +positive cash flow means you are spending more than you make, since +income is always a negative figure. To see your current cash flow, use +this command: + + ledger balance ^income ^expenses + + Another common question to ask of your expenses is: How much do I +spend each month on X? Ledger provides a simple way of displaying +monthly totals for any account. Here is an example that summarizes +your monthly automobile expenses: + + ledger -M register expenses:auto + + This assumes, of course, that you use account names like +`Expenses:Auto:Gas' and `Expenses:Auto:Repair'. + +3.2.1 Tracking reimbursable expenses +------------------------------------ + +Sometimes you will want to spend money on behalf of someone else, which +will eventually get repaid. Since the money is still "yours", it is +really an asset. And since the expenditure was for someone else, you +don't want it contaminating your Expenses reports. You will need to +keep an account for tracking reimbursements. + + This is fairly easy to do in ledger. When spending the money, spend +it _to_ your Assets:Reimbursements, using a different account for each +person or business that you spend money for. For example: + + 2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard + + This shows $100.00 spent on a MasterCard at Circuit City, with the +expense was made on behalf of Company XYZ. Later, when Company XYZ +pays the amount back, the money will transfer from that reimbursement +account back to a regular asset account: + + 2004/09/29 Company XYZ + Assets:Checking $100.00 + Assets:Reimbursements:Company XYZ + + This deposits the money owed from Company XYZ into a checking +account, presumably because they paid the amount back with a check. + + But what to do if you run your own business, and you want to keep +track of expenses made on your own behalf, while still tracking +everything in a single ledger file? This is more complex, because you +need to track two separate things: 1) The fact that the money should be +reimbursed to you, and 2) What the expense account was, so that you can +later determine where your company is spending its money. + + This kind of transaction is best handled with mirrored transactions +in two different files, one for your personal accounts, and one for your +company accounts. But keeping them in one file involves the same kinds +of transactions, so those are what is shown here. First, the personal +entry, which shows the need for reimbursement: + + 2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard + + This is the same as above, except that you own Company XYZ, and are +keeping track of its expenses in the same ledger file. This entry +should be immediately followed by an equivalent entry, which shows the +kind of expense, and also notes the fact that $100.00 is now payable to +you: + + 2004/09/29 Circuit City + Company XYZ:Expenses:Computer:Software $100.00 + Company XYZ:Accounts Payable:Your Name + + This second entry shows that Company XYZ has just spent $100.00 on +software, and that this $100.00 came from Your Name, which must be paid +back. + + These two entries can also be merged, to make things a little +clearer. Note that all amounts must be specified now: + + 2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard $-100.00 + Company XYZ:Expenses:Computer:Software $100.00 + Company XYZ:Accounts Payable:Your Name $-100.00 + + To "pay back" the reimbursement, just reverse the order of +everything, except this time drawing the money from a company asset, +paying it to accounts payable, and then drawing it again from the +reimbursement account, and paying it to your personal asset account. +It's easier shown than said: + + 2004/10/15 Company XYZ + Assets:Checking $100.00 + Assets:Reimbursements:Company XYZ $-100.00 + Company XYZ:Accounts Payable:Your Name $100.00 + Company XYZ:Assets:Checking $-100.00 + + And now the reimbursements account is paid off, accounts payable is +paid off, and $100.00 has been effectively transferred from the +company's checking account to your personal checking account. The +money simply "waited"--in both `Assets:Reimbursements:Company XYZ', and +`Company XYZ:Accounts Payable:Your Name'--until such time as it could +be paid off. + + The value of tracking expenses from both sides like that is that you +do not contaminate your personal expense report with expenses made on +behalf of others, while at the same time making it possible to generate +accurate reports of your company's expenditures. It is more verbose +than just paying for things with your personal assets, but it gives you +a very accurate information trail. + + The advantage to keep these doubled entries together is that they +always stay in sync. The advantage to keeping them apart is that it +clarifies the transfer's point of view. To keep the transactions in +separate files, just separate the two entries that were joined above. +For example, for both the expense and the pay-back shown above, the +following four entries would be created. Two in your personal ledger +file: + + 2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard $-100.00 + + 2004/10/15 Company XYZ + Assets:Checking $100.00 + Assets:Reimbursements:Company XYZ $-100.00 + + And two in your company ledger file: + + !account Company XYZ + + 2004/09/29 Circuit City + Expenses:Computer:Software $100.00 + Accounts Payable:Your Name $-100.00 + + 2004/10/15 Company XYZ + Accounts Payable:Your Name $100.00 + Assets:Checking $-100.00 + + !end + + (Note: The `!account' above means that all accounts mentioned in the +file are children of that account. In this case it means that all +activity in the file relates to Company XYZ). + + After creating these entries, you will always know that $100.00 was +spent using your MasterCard on behalf of Company XYZ, and that Company +XYZ spent the money on computer software and paid it back about two +weeks later. + + +File: ledger.info, Node: Commodities and Currencies, Next: Accounts and Inventories, Prev: Assets and Liabilities, Up: Keeping a ledger + +3.3 Commodities and Currencies +============================== + +Ledger makes no assumptions about the commodities you use; it only +requires that you specify a commodity. The commodity may be any +non-numeric string that does not contain a period, comma, forward slash +or at-sign. It may appear before or after the amount, although it is +assumed that symbols appearing before the amount refer to currencies, +while non-joined symbols appearing after the amount refer to +commodities. Here are some valid currency and commodity specifiers: + + $20.00 ; currency: twenty US dollars + 40 AAPL ; commodity: 40 shares of Apple stock + 60 DM ; currency: 60 Deutsch Mark + £50 ; currency: 50 British pounds + 50 EUR ; currency: 50 Euros (or use appropriate symbol) + + Ledger will examine the first use of any commodity to determine how +that commodity should be printed on reports. It pays attention to +whether the name of commodity was separated from the amount, whether it +came before or after, the precision used in specifying the amount, +whether thousand marks were used, etc. This is done so that printing +the commodity looks the same as the way you use it. + + An account may contain multiple commodities, in which case it will +have separate totals for each. For example, if your brokerage account +contains both cash, gold, and several stock quantities, the balance +might look like: + + $200.00 + 100.00 AU + AAPL 40 + BORL 100 + FEQTX 50 Assets:Brokerage + + This balance report shows how much of each commodity is in your +brokerage account. + + Sometimes, you will want to know the current street value of your +balance, and not the commodity totals. For this to happen, you must +specify what the current price is for each commodity. The price can be +any commodity, in which case the balance will be computed in terms of +that commodity. The usual way to specify prices is with a price +history file, which might look like this: + + P 2004/06/21 02:18:01 FEQTX $22.49 + P 2004/06/21 02:18:01 BORL $6.20 + P 2004/06/21 02:18:02 AAPL $32.91 + P 2004/06/21 02:18:02 AU $400.00 + + Specify the price history to use with the `--price-db' option, with +the `-V' option to report in terms of current market value: + + ledger --price-db prices.db -V balance brokerage + + The balance for your brokerage account will be reported in US +dollars, since the prices database uses that currency. + + $40880.00 Assets:Brokerage + + You can convert from any commodity to any other commodity. Let's say +you had $5000 in your checking account, and for whatever reason you +wanted to know many ounces of gold that would buy, in terms of the +current price of gold: + + ledger -T "{1 AU}*(O/P{1 AU})" balance checking + + Although the total expression appears complex, it is simply saying +that the reported total should be in multiples of AU units, where the +quantity is the account total divided by the price of one AU. Without +the initial multiplication, the reported total would still use the +dollars commodity, since multiplying or dividing amounts always keeps +the left value's commodity. The result of this command might be: + + 14.01 AU Assets:Checking + +3.3.1 Commodity price histories +------------------------------- + +Whenever a commodity is purchased using a different commodity (such as +a share of common stock using dollars), it establishes a price for that +commodity on that day. It is also possible, by recording price details +in a ledger file, to specify other prices for commodities at any given +time. Such price entries might look like those below: + + P 2004/06/21 02:17:58 TWCUX $27.76 + P 2004/06/21 02:17:59 AGTHX $25.41 + P 2004/06/21 02:18:00 OPTFX $39.31 + P 2004/06/21 02:18:01 FEQTX $22.49 + P 2004/06/21 02:18:02 AAPL $32.91 + + By default, ledger will not consider commodity prices when generating +its various reports. It will always report balances in terms of the +commodity total, rather than the current value of those commodities. +To enable pricing reports, use one of the commodity reporting options. + +3.3.2 Commodity equivalencies +----------------------------- + +Sometimes a commodity has several forms which are all equivalent. An +example of this is time. Whether tracked in terms of minutes, hours or +days, it should be possible to convert between the various forms. +Doing this requires the use of commodity equivalencies. + + For example, you might have the following two transactions, one which +transfers an hour of time into a `Billable' account, and another which +decreases the same account by ten minutes. The resulting report will +indicate that fifty minutes remain: + + 2005/10/01 Work done for company + Billable:Client 1h + Project:XYZ + + 2005/10/02 Return ten minutes to the project + Project:XYZ 10m + Billable:Client + + Reporting the balance for this ledger file produces: + + 50.0m Billable:Client + -50.0m Project:XYZ + + This example works because ledger already knows how to handle +seconds, minutes and hours, as part of its time tracking support. +Defining other equivalencies is simple. The following is an example +that creates data equivalencies, helpful for tracking bytes, kilobytes, +megabytes, and more: + + C 1.00 Kb = 1024 b + C 1.00 Mb = 1024 Kb + C 1.00 Gb = 1024 Mb + C 1.00 Tb = 1024 Gb + + Each of these definitions correlates a commodity (such as `Kb') and +a default precision, with a certain quantity of another commodity. In +the above example, kilobytes are reporetd with two decimal places of +precision and each kilobyte is equal to 1024 bytes. + + Equivalency chains can be as long as desired. Whenever a commodity +would report as a decimal amount (less than `1.00'), the next smallest +commodity is used. If a commodity could be reported in terms of a +higher commodity without resulting to a partial fraction, then the +larger commodity is used. + + +File: ledger.info, Node: Accounts and Inventories, Next: Understanding Equity, Prev: Commodities and Currencies, Up: Keeping a ledger + +3.4 Accounts and Inventories +============================ + +Since Ledger's accounts and commodity system is so flexible, you can +have accounts that don't really exist, and use commodities that no one +else recognizes. For example, let's say you are buying and selling +various items in EverQuest, and want to keep track of them using a +ledger. Just add items of whatever quantity you wish into your +EverQuest account: + + 9/29 Get some stuff at the Inn + Places:Black's Tavern -3 Apples + Places:Black's Tavern -5 Steaks + EverQuest:Inventory + + Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The +amounts are negative, because you are taking _from_ Black's Tavern in +order to add to your Inventory account. Note that you don't have to +use `Places:Black's Tavern' as the source account. You could use +`EverQuest:System' to represent the fact that you acquired them online. +The only purpose for choosing one kind of source account over another +is for generate more informative reports later on. The more you know, +the better analysis you can perform. + + If you later sell some of these items to another player, the entry +would look like: + + 10/2 Sturm Brightblade + EverQuest:Inventory -2 Steaks + EverQuest:Inventory 15 Gold + + Now you've turned 2 steaks into 15 gold, courtesy of your customer, +Sturm Brightblade. + + +File: ledger.info, Node: Understanding Equity, Next: Dealing with Petty Cash, Prev: Accounts and Inventories, Up: Keeping a ledger + +3.5 Understanding Equity +======================== + +The most confusing entry in any ledger will be your equity account-- +because starting balances can't come out of nowhere. + + When you first start your ledger, you will likely already have money +in some of your accounts. Let's say there's $100 in your checking +account; then add an entry to your ledger to reflect this amount. +Where will money come from? The answer: your equity. + + 10/2 Opening Balance + Assets:Checking $100.00 + Equity:Opening Balances + + But what is equity? You may have heard of equity when people talked +about house mortgages, as "the part of the house that you own". +Basically, equity is like the value of something. If you own a car +worth $5000, then you have $5000 in equity in that car. In order to +turn that car (a commodity) into a cash flow, or a credit to your bank +account, you will have to debit the equity by selling it. + + When you start a ledger, you are probably already worth something. +Your net worth is your current equity. By transferring the money in +the ledger from your equity to your bank accounts, you are crediting +the ledger account based on your prior equity. That is why, when you +look at the balance report, you will see a large negative number for +Equity that never changes: Because that is what you were worth (what +you debited from yourself in order to start the ledger) before the +money started moving around. If the total positive value of your +assets is greater than the absolute value of your starting equity, it +means you are making money. + + Clear as mud? Keep thinking about it. Until you figure it out, put +`-Equity' at the end of your balance command, to remove the confusing +figure from the total. + + +File: ledger.info, Node: Dealing with Petty Cash, Next: Working with multiple funds and accounts, Prev: Understanding Equity, Up: Keeping a ledger + +3.6 Dealing with Petty Cash +=========================== + +Something that stops many people from keeping a ledger at all is the +insanity of tracking small cash expenses. They rarely generate a +receipt, and there are often a lot of small transactions, rather than a +few large ones, as with checks. + + One solution is: don't bother. Move your spending to a debit card, +but in general ignore cash. Once you withdraw it from the ATM, mark it +as already spent to an `Expenses:Cash' category: + + 2004/03/15 ATM + Expenses:Cash $100.00 + Assets:Checking + + If at some point you make a large cash expense that you want to +track, just "move" the amount of the expense from `Expenses:Cash' into +the target account: + + 2004/03/20 Somebody + Expenses:Food $65.00 + Expenses:Cash + + This way, you can still track large cash expenses, while ignoring all +of the smaller ones. + + +File: ledger.info, Node: Working with multiple funds and accounts, Next: Archiving previous years, Prev: Dealing with Petty Cash, Up: Keeping a ledger + +3.7 Working with multiple funds and accounts +============================================ + +There are situations when the accounts you're tracking are different +between your clients and the financial institutions where money is +kept. An example of this is working as the treasurer for a religious +institution. From the secular point of view, you might be working with +three different accounts: + + * Checking + + * Savings + + * Credit Card + + From a religious point of view, the community expects to divide its +resources into multiple "funds", from which it makes purchases or +reserves resources for later: + + * School fund + + * Building fund + + * Community fund + + The problem with this kind of setup is that when you spend money, it +comes from two or more places at once: the account and the fund. And +yet, the correlation of amounts between funds and accounts is rarely +one-to-one. What if the school fund has `$500.00', but `$400.00' of +that comes from Checking, and `$100.00' from Savings? + + Traditional finance packages require that the money reside in only +one place. But there are really two "views" of the data: from the +account point of view and from the fund point of view - yet both sets +should reflect the same overall expenses and cash flow. It's simply +where the money resides that differs. + + This situation can be handled one of two ways. The first is using +virtual transactions to represent the fact that money is moving to and +from two kind of accounts at the same time: + + 2004/03/20 Contributions + Assets:Checking $500.00 + Income:Donations + + 2004/03/25 Distribution of donations + [Funds:School] $300.00 + [Funds:Building] $200.00 + [Assets:Checking] $-500.00 + + The use of square brackets in the second entry ensures that the +virtual transactions balance to zero. Now money can be spent directly +from a fund at the same time as money is drawn from a physical account: + + 2004/03/25 Payment for books (paid from Checking) + Expenses:Books $100.00 + Assets:Checking $-100.00 + (Funds:School) $-100.00 + + When reports are generated, by default they'll appear in terms of the +funds. In this case, you will likely want to mask out your `Assets' +account, because otherwise the balance won't make much sense: + + ledger bal -^Assets + + If the `--real' option is used, the report will be in terms of the +real accounts: + + ledger --real bal + + If more asset accounts are needed as the source of a transaction, +just list them as you would normally, for example: + + 2004/03/25 Payment for books (paid from Checking) + Expenses:Books $100.00 + Assets:Checking $-50.00 + Liabilities:Credit Card $-50.00 + (Funds:School) $-100.00 + + The second way of tracking funds is to use entry codes. In this +respect the codes become like virtual accounts that embrace the entire +set of transactions. Basically, we are associating an entry with a +fund by setting its code. Here are two entries that desposit money +into, and spend money from, the `Funds:School' fund: + + 2004/03/25 (Funds:School) Donations + Assets:Checking $100.00 + Income:Donations + + 2004/04/25 (Funds:School) Payment for books + Expenses:Books $50.00 + Assets:Checking + + Note how the accounts now relate only to the real accounts, and any +balance or registers reports will reflect this. That the entries +relate to a particular fund is kept only in the code. + + How does this become a fund report? By using the `--code-as-payee' +option, you can generate a register report where the payee for each +transaction shows the code. Alone, this is not terribly interesting; +but when combined with the `--by-payee' option, you will now see +account subtotals for any transactions related to a specific fund. So, +to see the current monetary balances of all funds, the command would be: + + ledger --code-as-payee -P reg ^Assets + + Or to see a particular funds expenses, the `School' fund in this +case: + + ledger --code-as-payee -P reg ^Expenses -- School + + Both approaches yield different kinds of flexibility, depending on +how you prefer to think of your funds: as virtual accounts, or as tags +associated with particular entries. Your own tastes will decide which +is best for your situation. + + +File: ledger.info, Node: Archiving previous years, Next: Virtual transactions, Prev: Working with multiple funds and accounts, Up: Keeping a ledger + +3.8 Archiving previous years +============================ + +After a while, your ledger can get to be pretty large. While this will +not slow down the ledger program much--it's designed to process ledger +files very quickly--things can start to feel "messy"; and it's a +universal complaint that when finances feel messy, people avoid them. + + Thus, archiving the data from previous years into their own files can +offer a sense of completion, and freedom from the past. But how to +best accomplish this with the ledger program? There are two commands +that make it very simple: `print', and `equity'. + + Let's take an example file, with data ranging from year 2000 until +2004. We want to archive years 2000 and 2001 to their own file, +leaving just 2003 and 2004 in the current file. So, use `print' to +output all the earlier entries to a file called `ledger-old.dat': + + ledger -f ledger.dat -b 2000 -e 2001 print > ledger-old.dat + + To delete older data from the current ledger file, use `print' +again, this time specifying year 2002 as the starting date: + + ledger -f ledger.dat -b 2002 print > x + mv x ledger.dat + + However, now the current file contains _only_ transactions from 2002 +onward, which will not yield accurate present-day balances, because the +net income from previous years is no longer being tallied. To +compensate for this, we must append an equity report for the old ledger +at the beginning of the new one: + + ledger -f ledger-old.dat equity > equity.dat + cat equity.dat ledger.dat > x + mv x ledger.dat + rm equity.dat + + Now the balances reported from `ledger.dat' are identical to what +they were before the data was split. + + How often should you split your ledger? You never need to, if you +don't want to. Even eighty years of data will not slow down ledger +much--and that's just using present day hardware! Or, you can keep the +previous and current year in one file, and each year before that in its +own file. It's really up to you, and how you want to organize your +finances. For those who also keep an accurate paper trail, it might be +useful to archive the older years to their own files, then burn those +files to a CD to keep with the paper records--along with any electronic +statements received during the year. In the arena of organization, +just keep in mind this maxim: Do whatever keeps you doing it. + + +File: ledger.info, Node: Virtual transactions, Next: Automated transactions, Prev: Archiving previous years, Up: Keeping a ledger + +3.9 Virtual transactions +======================== + +A virtual transaction is when you, in your mind, see money as moving to +a certain place, when in reality that money has not moved at all. +There are several scenarios in which this type of tracking comes in +handy, and each of them will be discussed in detail. + + To enter a virtual transaction, surround the account name in +parentheses. This form of usage does not need to balance. However, if +you want to ensure the virtual transaction balances with other virtual +transactions in the same entry, use square brackets. For example: + + 10/2 Paycheck + Assets:Checking $1000.00 + Income:Salary $-1000.00 + (Debt:Alimony) $200.00 + + In this example, after receiving a paycheck an alimony debt is +increased--even though no money has moved around yet. + + 10/2 Paycheck + Assets:Checking $1000.00 + Income:Salary $-1000.00 + [Savings:Trip] $200.00 + [Assets:Checking] $-200.00 + + In this example, $200 has been deducted from checking toward savings +for a trip. It will appear as though the money has been moved from the +account into `Savings:Trip', although no money has actually moved +anywhere. + + When balances are displayed, virtual transactions will be factored +in. To view balances without any virtual balances factored in, using +the `-R' flag, for "reality". + + +File: ledger.info, Node: Automated transactions, Next: Using Emacs to Keep Your Ledger, Prev: Virtual transactions, Up: Keeping a ledger + +3.10 Automated transactions +=========================== + +As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire +assets. It is similar to tithing for Jews and Christians, or to Zakát +for Muslims. The exact details of computing Huqúqu'lláh are somewhat +complex, but if you have further interest, please consult the Web. + + Ledger makes this otherwise difficult law very easy. Just set up an +automated transaction at the top of your ledger file: + + ; This automated entry will compute Huqúqu'lláh based on this + ; journal's transactions. Any that match will affect the + ; Liabilities:Huququ'llah account by 19% of the value of that + ; transaction. + + = /^(?:Income:|Expenses:(?:Business|Rent$|Furnishings|Taxes|Insurance))/ + (Liabilities:Huququ'llah) 0.19 + + This automated transaction works by looking at each transaction in +the ledger file. If any match the given value expression, 19% of the +transaction's value is applied to the `Liabilities:Huququ'llah' +account. So, if $1000 is earned from `Income:Salary', $190 is added to +`Liabilities:Huqúqu'lláh'; if $1000 is spent on Rent, $190 is +subtracted. The ultimate balance of Huqúqu'lláh reflects how much is +owed in order to fulfill one's obligation to Huqúqu'lláh. When ready +to pay, just write a check to cover the amount shown in +`Liabilities:Huququ'llah'. That entry would look like: + + 2003/01/01 (101) Baha'i Huqúqu'lláh Trust + Liabilities:Huququ'llah $1,000.00 + Assets:Checking + + That's it. To see how much Huqúq is currently owed based on your +ledger entries, use: + + ledger balance Liabilities:Huquq + + This works fine, but omits one aspect of the law: that Huquq is only +due once the liability exceeds the value of 19 mithqáls of gold (which +is roughly 2.22 ounces). So what we want is for the liability to +appear in the balance report only when it exceeds the present day value +of 2.22 ounces of gold. This can be accomplished using the command: + + ledger -Q -t "/Liab.*Huquq/?(a/P{2.22 AU}<={-1.0}&a):a" -s bal liab + + With this command, the current price for gold is downloaded, and the +Huqúqu'lláh is reported only if its value exceeds that of 2.22 ounces +of gold. If you wish the liability to be reflected in the parent +subtotal either way, use this instead: + + ledger -Q -T "/Liab.*Huquq/?(O/P{2.22 AU}<={-1.0}&O):O" -s bal liab + + In some cases, you may wish to refer to the account of whichever +transaction matched your automated entry's value expression. To do +this, use the special account name `$account': + + = /^Some:Long:Account:Name/ + [$account] -0.10 + [Savings] 0.10 + + This example causes 10% of the matching account's total to be +deferred to the `Savings' account--as a balanced virtual transaction, +which may be excluded from reports by using `--real'. + + +File: ledger.info, Node: Using Emacs to Keep Your Ledger, Next: Using GnuCash to Keep Your Ledger, Prev: Automated transactions, Up: Keeping a ledger + +3.11 Using Emacs to Keep Your Ledger +==================================== + +In the Ledger tarball is an Emacs module, `ledger.el'. This module +makes the process of keeping a text ledger much easier for Emacs users. +I recommend putting this at the top of your ledger file: + + ; -*-ledger-*- + + And this in your `.emacs' file, after copying `ledger.el' to your +`site-lisp' directory: + + (load "ledger") + + Now when you edit your ledger file, it will be in `ledger-mode'. +`ledger-mode' adds these commands: + +*C-c C-a* + For quickly adding new entries based on the form of older ones (see + previous section). + +*C-c C-c* + Toggles the "cleared" flag of the transaction under point. + +*C-c C-d* + Delete the entry under point. + +*C-c C-r* + Reconciles an account by displaying the transactions in another + buffer, where simply hitting the spacebar will toggle the pending + flag of the transaction in the ledger. Once all the appropriate + transactions have been marked, press C-c C-c in the reconcile + buffer to "commit" the reconciliation, which will mark all of the + entries as cleared, and display the new cleared balance in the + minibuffer. + +*C-c C-m* + Set the default month for new entries added with C-c C-a. This is + handy if you have a large number of transactions to enter from a + previous month. + +*C-c C-y* + Set the default year for new entries added with C-c C-a. This is + handy if you have a large number of transactions to enter from a + previous year. + + Once you enter the reconcile buffer, there are several key commands +available: + +*RET* + Visit the ledger file entry corresponding to the reconcile entry. + +*C-c C-c* + Commit the reconcialation. This marks all of the marked + transactions as "cleared", saves the ledger file, and then + displays the new cleared balance. + +*C-l* + Refresh the reconcile buffer by re-reading transactions from the + ledger data file. + +*SPC* + Toggle the transaction under point as cleared. + +*a* + Add a new entry to the ledger data file, and refresh the reconcile + buffer to include its transactions (if the entry is added to the + same account as the one being reconciled). + +*d* + Delete the entry related to the transaction under point. Note: + This may result in multiple transactions being deleted. + +*n* + Move to the next line. + +*p* + Move to the previous line. + +*C-c C-r* + +*r* + Attempt to auto-reconcile the transactions to the entered balance. + If it can do so, it will mark all those transactions as pending + that would yield the specified balance. + +*C-x C-s* + +*s* + Save the ledger data file, and show the current cleared balance for + the account being reconciled. + +*q* + Quit the reconcile buffer. + + There is also an `emacs' command which can be used to output reports +in a format directly `read'-able from Emacs Lisp. + + +File: ledger.info, Node: Using GnuCash to Keep Your Ledger, Next: Using timeclock to record billable time, Prev: Using Emacs to Keep Your Ledger, Up: Keeping a ledger + +3.12 Using GnuCash to Keep Your Ledger +====================================== + +The Ledger tool is fast and simple, but it offers no custom method for +actually editing the ledger. It assumes you know how to use a text +editor, and like doing so. Perhaps an Emacs mode will appear someday +soon to make editing Ledger's data files much easier. + + Until then, you are free to use GnuCash to maintain your ledger, and +the Ledger program for querying and reporting on the contents of that +ledger. It takes a little longer to parse the XML data format that +GnuCash uses, but the end result is identical. + + Then again, why would anyone use a Gnome-centric, 35 megabyte +behemoth to edit their data, and a one megabyte binary to query it? + + +File: ledger.info, Node: Using timeclock to record billable time, Prev: Using GnuCash to Keep Your Ledger, Up: Keeping a ledger + +3.13 Using timeclock to record billable time +============================================ + +The timeclock tool makes it easy to track time events, like clocking +into and out of a particular job. These events accumulate in a timelog +file. + + Each in/out event may have an optional description. If the "in" +description is a ledger account name, these in/out pairs may be viewed +as virtual transactions, adding time commodities (hours) to that +account. + + For example, the command-line version of the timeclock tool could be +used to begin a timelog file like: + + export TIMELOG=$HOME/.timelog + ti ClientOne category + sleep 10 + to waited for ten seconds + + The `.timelog' file now contains: + + i 2004/10/06 15:21:00 ClientOne category + o 2004/10/06 15:21:10 waited for ten seconds + + Ledger parses this directly, as if it had seen the following entry: + + 2004/10/06 category + (ClientOne) 10s + + In other words, the timelog event pair is seen as adding 0.00277h +(ten seconds) worth of time to the `ClientOne' account. This would be +considered billable time, which later could be invoiced and credited to +accounts receivable: + + 2004/11/01 (INV#1) ClientOne, Inc. + Receivable:ClientOne $0.10 + ClientOne -0.00277h @ $35.00 + + The above transaction converts the clocked time into an invoice for +the time spent, at an hourly rate of $35. Once the invoice is paid, +the money is deposited from the receivable account into a checking +account: + + 2004/12/01 ClientOne, Inc. + Assets:Checking $0.10 + Receivable:ClientOne + + And now the time spent has been turned into hard cash in the checking +account. + + The advantage to using timeclock and invoicing to bill time is that +you will always know, by looking at the balance report, exactly how +much unbilled and unpaid time you've spent working for any particular +client. + + I like to `!include' my timelog at the top of my company's +accounting ledger, with the attached prefix `Billable': + + ; -*-ledger-*- + + ; This is the ledger file for my company. But first, include the + ; timelog data, entering all of the time events within the umbrella + ; account "Billable". + + !account Billable + !include /home/johnw/.timelog + !end + + ; Here follows this fiscal year's transactions for the company. + + 2004/11/01 (INV#1) ClientOne, Inc. + Receivable:ClientOne $0.10 + Billable:ClientOne -0.00277h @ $35.00 + + 2004/12/01 ClientOne, Inc. + Assets:Checking $0.10 + Receivable:ClientOne + + +File: ledger.info, Node: Using XML, Prev: Keeping a ledger, Up: Top + +4 Using XML +*********** + +By default, Ledger uses a human-readable data format, and displays its +reports in a manner meant to be read on screen. For the purpose of +writing tools which use Ledger, however, it is possible to read and +display data using XML. This chapter documents that format. + + The general format used for Ledger data is: + + + + ... + ... + ...... + + + The data stream is enclosed in a `ledger' tag, which contains a +series of one or more entries. Each `entry' describes the entry and +contains a series of one or more transactions: + + + 2004/03/01 + + 100 + John Wiegley + + ... + ... + ...... + + + + The date format for `en:date' is always `YYYY/MM/DD'. The +`en:cleared' tag is optional, and indicates whether the transaction has +been cleared or not. There is also an `en:pending' tag, for marking +pending transactions. The `en:code' and `en:payee' tags both contain +whatever text the user wishes. + + After the initial entry data, there must follow a set of transactions +marked with `en:transactions'. Typically these transactions will all +balance each other, but if not they will be automatically balanced into +an account named `'. + + Within the `en:transactions' tag is a series of one or more +`transaction''s, which have the following form: + + + Expenses:Computer:Hardware + + + + $ + 90.00 + + + + + + This is a basic transaction. It may also be begin with `tr:virtual' +and/or `tr:generated' tags, to indicate virtual and auto-generated +transactions. Then follows the `tr:account' tag, which contains the +full name of the account the transaction is related to. Colons +separate parent from child in an account name. + + Lastly follows the amount of the transaction, indicated by +`tr:amount'. Within this tag is a `value' tag, of which there are four +different kinds, each with its own format: + + 1. boolean + + 2. integer + + 3. amount + + 4. balance + + The format of a boolean value is `true' or `false' surrounded by a +`boolean' tag, for example: + + true + + The format of an integer value is the numerical value surrounded by +an `integer' tag, for example: + + 12036 + + The format of an amount contains two members, the commodity and the +quantity. The commodity can have a set of flags that indicate how to +display it. The meaning of the flags (all of which are optional) are: + +*P* + The commodity is prefixed to the value. + +*S* + The commodity is separated from the value by a space. + +*T* + Thousands markers are used to display the amount. + +*E* + The format of the amount is European, with period used as a + thousands marker, and comma used as the decimal point. + + The actual quantity for an amount is an integer of arbitrary size. +Ledger uses the GNU multi-precision math library to handle such values. +The XML format assumes the reader to be equally capable. Here is an +example amount: + + + + $ + 90.00 + + + + Lastly, a balance value contains a series of amounts, each with a +different commodity. Unlike the name, such a value does need to +balance. It is called a balance because it sums several amounts. For +example: + + + + + $ + 90.00 + + + DM + 200.00 + + + + + That is the extent of the XML data format used by Ledger. It will +output such data if the `xml' command is used, and can read the same +data as long as the `expat' library was available when Ledger was built. + + + +Tag Table: +Node: Top1762 +Node: Introduction3440 +Ref: Introduction-Footnote-19380 +Node: Building the program9457 +Node: Getting help10004 +Node: Running Ledger10414 +Node: Usage overview11934 +Ref: Usage overview-Footnote-145404 +Ref: Usage overview-Footnote-245522 +Node: Commands45627 +Node: Options50856 +Node: Basic options51717 +Node: Report filtering53902 +Node: Output customization57782 +Node: Commodity reporting62687 +Node: Environment variables64785 +Node: Format strings65433 +Node: Value expressions70805 +Node: Period expressions77136 +Node: File format78724 +Node: Some typical queries83592 +Node: Budgeting and forecasting87314 +Node: Keeping a ledger90054 +Node: Stating where money goes92763 +Node: Assets and Liabilities95416 +Node: Commodities and Currencies103561 +Node: Accounts and Inventories109722 +Node: Understanding Equity111317 +Node: Dealing with Petty Cash113224 +Node: Working with multiple funds and accounts114321 +Node: Archiving previous years119037 +Node: Virtual transactions121561 +Node: Automated transactions123237 +Node: Using Emacs to Keep Your Ledger126257 +Node: Using GnuCash to Keep Your Ledger129328 +Node: Using timeclock to record billable time130237 +Node: Using XML132997 + +End Tag Table diff --git a/docs/ledger.texi b/docs/ledger.texi new file mode 100644 index 00000000..bd42a3b0 --- /dev/null +++ b/docs/ledger.texi @@ -0,0 +1,3960 @@ +\input texinfo @c -*-texinfo-*- + +@setfilename ledger.info +@settitle Ledger: Command-Line Accounting + +@dircategory User Applications +@copying +Copyright (c) 2003-2006, John Wiegley. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of New Artisans LLC nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +@end copying + +@documentencoding iso-8859-1 + +@iftex +@finalout +@end iftex + +@titlepage +@title Ledger: Command-Line Accounting +@author John Wiegley +@end titlepage + +@direntry +* Ledger: (ledger). Command Line Accounting +@end direntry + +@contents + +@ifnottex +@node Top, Introduction, (dir), (dir) +@top Overview + +@insertcopying +@end ifnottex + +@menu +* Introduction:: +* Running Ledger:: +* Keeping a ledger:: +* Using XML:: +@end menu + +@node Introduction, Running Ledger, Top, Top +@chapter Introduction + +Ledger is an accounting tool with the moxie to exist. It provides no +bells or whistles, and returns the user to the days before user +interfaces were even a twinkling in their father's CRT. + +What it does offer is a double-entry accounting ledger with all the +flexibility and muscle of its modern day cousins, without any of the +fat. Think of it as the Bran Muffin of accounting tools. + +To use it, you need to start keeping a ledger. This is the basis of +all accounting, and if you haven't started yet, now is the time to +learn. The little booklet that comes with your checkbook is a ledger, +so we'll describe double-entry accounting in terms of that. + +A checkbook ledger records debits (subtractions, or withdrawals) and +credits (additions, or deposits) with reference to a single account: +the checking account. Where the money comes from, and where it goes +to, are described in the payee field, where you write the person or +company's name. The ultimate aim of keeping a checkbook ledger is to +know how much money is available to spend. That's really the aim of +all ledgers. + +What computers add is the ability to walk through these transactions, +and tell you things about your spending habits; to let you devise +budgets and get control over your spending; to squirrel away money +into virtual savings account without having to physically move money +around; etc. As you keep your ledger, you are recording information +about your life and habits, and sometimes that information can start +telling you things you aren't aware of. Such is the aim of all good +accounting tools. + +The next step up from a checkbook ledger, is a ledger that keeps track +of all your accounts, not just checking. In such a ledger, you record +not only who gets paid---in the case of a debit---but where the money +came from. In a checkbook ledger, its assumed that all the money +comes from your checking account. But in a general ledger, you write +transaction two-lines: the source account and target account. +@emph{There must always be a debit from at least one account for every +credit made to another account}. This is what is meant by +``double-entry'' accounting: the ledger must always balance to zero, +with an equal number of debits and credits. + +For example, let's say you have a checking account and a brokerage +account, and you can write checks from both of them. Rather than keep +two checkbooks, you decide to use one ledger for both. In this +general ledger you need to record a payment to Pacific Bell for your +monthly phone bill. The cost is $23.00, let's say, and you want to +pay it from your checking account. In the general ledger you need to +say where the money came from, in addition to where it's going to. +The entry might look like this: + +@smallexample +9/29 BAL Pacific Bell $-200.00 $-200.00 + Equity:Opening Balances $200.00 +9/29 BAL Checking $100.00 $100.00 + Equity:Opening Balances $-100.00 +9/29 100 Pacific Bell $23.00 $223.00 + Checking $-23.00 $77.00 +@end smallexample + +The first line shows a payment to Pacific Bell for $23.00. Because +there is no ``balance'' in a general ledger---it's always zero---we +write in the total balance of all payments to ``Pacific Bell'', which +now is $223.00 (previously the balance was $200.00). This is done by +looking at the last entry for ``Pacific Bell'' in the ledger, adding +$23.00 to that amount, and writing the total in the balance column. +And the money came from ``Checking''---a withdrawal of $23.00---which +leaves the ending balance in ``Checking'' at $77.00. This is a very +manual procedure; but that's where computers come in... + +The transaction must balance to $0: $23 went to Pacific Bell, $23 came +from Checking. There is nothing left over to be accounted for, since +the money has simply moved from one account to another. This is the +basis of double-entry accounting: that money never pops in or out of +existence; it is always a transaction from one account to another. + +Keeping a general ledger is the same as keeping two separate ledgers: +One for Pacific Bell and one for Checking. In that case, each time a +payment is written into one, you write a corresponding withdrawal into +the other. This makes it easier to write in a ``running balance'', +since you don't have to look back at the last time the account was +referenced---but it also means having a lot of ledger books, if you +deal with multiple accounts. + +Enter the beauty of computerized accounting. The purpose of the +Ledger program is to make general ledger accounting simple, by keeping +track of the balances for you. Your only job is to enter the +transactions. If a transaction does not balance, Ledger displays an +error and indicates the incorrect transaction.@footnote{In some +special cases, it automatically balances this entry for you.} + +In summary, there are two aspects of Ledger use: updating the ledger +data file, and using the Ledger tool to view the summarized result of +your entries. + +And just for the sake of example---as a starting point for those who +want to dive in head-first---here are the ledger entries from above, +formatting as the ledger program wishes to see them: + +@smallexample +2004/09/29 Pacific Bell + Payable:Pacific Bell $-200.00 + Equity:Opening Balances + +2004/09/29 Checking + Accounts:Checking $100.00 + Equity:Opening Balances + +2004/09/29 Pacific Bell + Payable:Pacific Bell $23.00 + Accounts:Checking +@end smallexample + +The account balances and registers in this file, if saved as +@file{ledger.dat}, could be reported using: + +@example +$ ledger -f ledger.dat balance +$ ledger -f ledger.dat register checking +$ ledger -f ledger.dat register bell +@end example + +@menu +* Building the program:: +* Getting help:: +@end menu + +@node Building the program, Getting help, Introduction, Introduction +@section Building the program + +Ledger is written in ANSI C++, and should compile on any platform. It +depends on the GNU multiprecision integer library (libgmp), and the +Perl regular expression library (libpcre). It was developed using GNU +make and gcc 3.3, on a PowerBook running OS/X. + +To build and install once you have these libraries on your system, +enter these commands: + +@example +./configure && make install +@end example + +@node Getting help, , Building the program, Introduction +@section Getting help + +If you need help on how to use Ledger, or run into problems, you can +just the Ledger mailing list at the following Web address: + +@example +https://lists.sourceforge.net/lists/listinfo/ledger-discuss +@end example + +You can also find help at the @samp{#ledger} channel on the IRC server +@samp{irc.freenode.net}. + +@node Running Ledger, Keeping a ledger, Introduction, Top +@chapter Running Ledger + +Ledger has a very simple command-line interface, named---enticing +enough---@command{ledger}. It supports a few reporting commands, and +a large number of options for refining the output from those commands. +The basic syntax of any ledger command is: + +@example +ledger [OPTIONS...] COMMAND [ARGS...] +@end example + +Command options must always precede the command word. After the +command word there may appear any number of arguments. For most +commands, these arguments are regular expressions that cause the +output to relate only to transactions matching those regular +expressions. For the @command{entry} command, the arguments have a +special meaning, described below. + +The regular expressions arguments always match the account name that a +transaction refers to. To match on the payee of the entry instead, +precede the regular expression with @samp{--}. For example, the +following balance command reports account totals for rent, food and +movies, but only those whose payee matches Freddie: + +@example +ledger bal rent food movies -- freddie +@end example + +There are many, many command options available with the +@command{ledger} command, and it takes a while to master them. +However, none of them are required to use the basic reporting +commands. + +@menu +* Usage overview:: +* Commands:: +* Options:: +* Format strings:: +* Value expressions:: +* Period expressions:: +* File format:: +* Some typical queries:: +* Budgeting and forecasting:: +@end menu + +@node Usage overview, Commands, Running Ledger, Running Ledger +@section Usage overview + +Before getting into the details of how to run Ledger, it will be +easier to introduce the features in the context of their typical +usage. To that end, this section presents a series of recipes, +gradually introducing all of the command-line features of Ledger. + +For the purpose of these examples, assume the environment variable +@var{LEDGER} is set to the file @file{sample.dat} (which is included +in the distribution), and that the contents of that file are: + +@smallexample += /^Expenses:Books/ + (Liabilities:Taxes) -0.10 + +~ Monthly + Assets:Bank:Checking $500.00 + Income:Salary + +2004/05/01 * Checking balance + Assets:Bank:Checking $1,000.00 + Equity:Opening Balances + +2004/05/01 * Investment balance + Assets:Brokerage 50 AAPL @ $30.00 + Equity:Opening Balances + +2004/05/14 * Pay day + Assets:Bank:Checking $500.00 + Income:Salary + +2004/05/27 Book Store + Expenses:Books $20.00 + Liabilities:MasterCard + +2004/05/27 (100) Credit card company + Liabilities:MasterCard $20.00 + Assets:Bank:Checking +@end smallexample + +This sample file demonstrates a basic principle of accounting which it +is recommended you follow: Keep all of your accounts under five parent +Assets, Liabilities, Income, Expenses and Equity. It is important to +do so in order to make sense out of the following examples. + +@subsection Checking balances + +Ledger has seven basic commands, but by far the most often used are +@command{balance} and @command{register}. To see a summary balance of +all accounts, use: + +@example +ledger bal +@end example + +@command{bal} is a short-hand for @command{balance}. This command +prints out the summary totals of the five parent accounts used in +@file{sample.dat}: + +@smallexample + $1,480.00 + 50 AAPL Assets + $-2,500.00 Equity + $20.00 Expenses + $-500.00 Income + $-2.00 Liabilities +-------------------- + $-1,502.00 + 50 AAPL +@end smallexample + +None of the child accounts are shown, just the parent account totals. +We can see that in @samp{Assets} there is $1,480.00, and 50 shares of +Apple stock. There is also a negative grand total. Usually the grand +total is zero, which means that all accounts balance@footnote{It is +impossible for accounts not to balance in ledger; it reports an error +if a transaction does not balance}. In this case, since the 50 shares +of Apple stock cost $1,500.00 dollars, then these two amounts balance +each other in the grand total. The extra $2.00 comes from a virtual +transaction being added by the automatic entry at the top of the file. +The entry is virtual because the account name was surrounded by +parentheses in an automatic entry. Automatic entries will be +discussed later, but first let's remove the virtual transaction from +the balance report by using the @option{--real} option: + +@example +ledger --real bal +@end example + +Now the report is: + +@smallexample + $1,480.00 + 50 AAPL Assets + $-2,500.00 Equity + $20.00 Expenses + $-500.00 Income +-------------------- + $-1,500.00 + 50 AAPL +@end smallexample + +Since the liability was a virtual transaction, it has dropped from the +report and we see that final total is balanced. + +But we only know that it balances because @file{sample.dat} is quite +simple, and we happen to know that the 50 shares of Apple stock cost +$1,500.00. We can verify that things really balance by reporting the +Apple shares in terms of their cost, instead of their quantity. To do +this requires the @option{--basis}, or @option{-B}, option: + +@example +ledger --real -B bal +@end example + +This command reports: + +@smallexample + $2,980.00 Assets + $-2,500.00 Equity + $20.00 Expenses + $-500.00 Income +@end smallexample + +With the basis cost option, the grand total has disappeared, as it is +now zero. The confirms that the cost of everything balances to zero, +@emph{which must always be true}. Reporting the real basis cost +should never yield a remainder@footnote{If it ever does, then +generated transactions are involved, which can be removed using +@option{--actual}}. + +@subsubsection Sub-account balances + +The totals reported by the balance command are only the topmost parent +accounts. To see the totals of all child accounts as well, use the +@option{-s} option: + +@example +ledger --real -B -s bal +@end example + +This reports: + +@smallexample + $2,980.00 Assets + $1,480.00 Bank:Checking + $1,500.00 Brokerage + $-2,500.00 Equity:Opening Balances + $20.00 Expenses:Books + $-500.00 Income:Salary +@end smallexample + +This shows that the @samp{Assets} total is made up from two child +account, but that the total for each of the other accounts comes from +one child account. + +Sometimes you may have a lot of children, nested very deeply, but only +want to report the first two levels. This can be done with a display +predicate, using a value expression. In the value expression, +@code{T} represents the reported total, and @code{l} is the display +level for the account: + +@example +ledger --real -B -d "T&l<=2" bal +@end example + +This reports: + +@smallexample + $2,980.00 Assets + $1,480.00 Bank + $1,500.00 Brokerage + $-2,500.00 Equity:Opening Balances + $20.00 Expenses:Books + $-500.00 Income:Salary +@end smallexample + +Instead of reporting @samp{Bank:Checking} as a child of @samp{Assets}, +it report only @samp{Bank}, since that account is a nesting level of +2, while @samp{Checking} is at level 3. + +To review the display predicate used---@code{T&l<=2}---this rather +terse expression means: Display an account only if it has a non-zero +total (@code{T}), and its nesting level is less than or equal to 2 +(@code{l<=2}). + +@subsubsection Specific account balances + +While reporting the totals for all accounts can be useful, most often +you will want to check the balance of a specific account or accounts. +To do this, put one or more account names after the balance command. +Since these names are really regular expressions, you can use partial +names if you wish: + +@example +ledger bal checking +@end example + +Reports: + +@smallexample + $1,480.00 Assets:Bank:Checking +@end smallexample + +Any number of names may be used: + +@example +ledger bal checking broker liab +@end example + +Reports: + +@smallexample + $1,480.00 Assets:Bank:Checking + 50 AAPL Assets:Brokerage + $-2.00 Liabilities +@end smallexample + +In this case no grand total is reported, because you are asking for +specific account balances. + +For those comfortable with regular expressions, any Perl regexp is +allowed: + +@example +ledger bal ^assets.*checking ^liab +@end example + +Reports: + +@smallexample + $1,480.00 Assets:Bank:Checking + $-2.00 Liabilities:Taxes +@end smallexample + +@subsection The register report + +While the @command{balance} command can be very handy for checking +account totals, by far the most powerful of Ledger's reporting tools +is the @command{register} command. In fact, internally both commands +use the same logic, but report the results differently: +@command{balance} shows the summary totals, while @command{register} +reports each transaction and how it contributes to that total. + +Paradoxically, the most basic form of @command{register} is almost +never used, since it displays every transaction: + +@example +ledger reg +@end example + +@command{reg} is a short-hand for @command{register}. This command +reports: + +@smallexample +2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 + Equity:Opening Balan.. $-1,000.00 0 +2004/05/01 Investment balance Assets:Brokerage 50 AAPL 50 AAPL + Equity:Opening Balan.. $-1,500.00 $-1,500.00 + 50 AAPL +2004/05/14 Pay day Assets:Bank:Checking $500.00 $-1,000.00 + 50 AAPL + Income:Salary $-500.00 $-1,500.00 + 50 AAPL +2004/05/27 Book Store Expenses:Books $20.00 $-1,480.00 + 50 AAPL + Liabilities:MasterCard $-20.00 $-1,500.00 + 50 AAPL + (Liabilities:Taxes) $-2.00 $-1,502.00 + 50 AAPL +2004/05/27 Credit card company Liabilities:MasterCard $20.00 $-1,482.00 + 50 AAPL + Assets:Bank:Checking $-20.00 $-1,502.00 + 50 AAPL +@end smallexample + +This rather verbose output shows every account transaction in +@file{sample.dat}, and how it affects the running total. The final +total is identical to what we saw with the plain @command{balance} +command. To see how things really balance, we can use @samp{--real +-B}, just as we did with @command{balance}: + +@example +ledger --real -B reg +@end example + +Reports: + +@smallexample +2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 + Equity:Opening Balan.. $-1,000.00 0 +2004/05/01 Investment balance Assets:Brokerage $1,500.00 $1,500.00 + Equity:Opening Balan.. $-1,500.00 0 +2004/05/14 Pay day Assets:Bank:Checking $500.00 $500.00 + Income:Salary $-500.00 0 +2004/05/27 Book Store Expenses:Books $20.00 $20.00 + Liabilities:MasterCard $-20.00 0 +2004/05/27 Credit card company Liabilities:MasterCard $20.00 $20.00 + Assets:Bank:Checking $-20.00 0 +@end smallexample + +Here we see that everything balances to zero in the end, as it must. + +@subsubsection Specific register queries + +The most common use of the register command is to summarize +transactions based on the account(s) they affect. Using +@file{sample.dat} as as example, we could look at all book purchases +using: + +@example +ledger reg books +@end example + +Reports: + +@smallexample +2004/05/29 Book Store Expenses:Books $20.00 $20.00 +@end smallexample + +If a double-dash (@samp{--}) occurs in the list of regular +expressions, any following arguments are matched against payee names, +instead of account names: + +@example +ledger reg ^liab -- credit +@end example + +Reports: + +@smallexample +2004/05/29 Credit card company Liabilities:MasterCard $20.00 $20.00 +@end smallexample + +There are many reporting options for tailoring which transactions are +found, and also how to summarize the various amounts and totals that +result. These are plumbed in greater depth below. + +@subsection Selecting transactions + +Although the easiest way to use the register is to report all the +transactions affecting a set of accounts, it can often result in more +information than you want. To cope with an ever-growing amount of +data, there are several options which can help you pinpoint your +report to exactly the transactions that interest you most. This is +called the ``calculation'' phase of Ledger. All of its related +options are documented under @option{--help-calc}. + +@subsubsection By date + +@c -c, --current show only current and past entries (not future) + +@option{--current}(@option{-c}) displays entries occurring on or +before the current date. Any entry recorded for a future date will be +ignored, as if it had not been seen. This is useful if you happen to +pre-record entries, but still wish to view your balances in terms of +what is available today. + +@c -b, --begin DATE set report begin date +@c -e, --end DATE set report end date + +@option{--begin DATE} (@option{-b DATE}) limits the report to only +those entries occurring on or after @var{DATE}. The running total in +the register will start at zero with the first transaction, even if +there are earlier entries. + +To limit the display only, but still add earlier transactions to the +running total, use the display expression @samp{-d 'd>=[DATE]'}): + +@example +ledger --basis -b may -d 'd>=[5/14]' reg ^assets +@end example + +Reports: + +@smallexample +2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 +2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 +@end smallexample + +In this example, the displayed transactions start from @samp{5/14}, +but the calculated total starts from the beginning of @samp{may}. + +@option{--end DATE} (@option{-e DATE}) states when reporting should +end, both calculation and display. The ending date is inclusive. + +The @var{DATE} argument to the @option{-b} and @option{-e} options can +be rather flexible. Assuming the current date to be November 15, +2004, then all of the following are equivalent: + +@example +ledger -b oct bal +ledger -b "this oct" bal +ledger -b 2004/10 bal +ledger -b 10 bal +ledger -b last bal +ledger -b "last month" bal +@end example + +@c -p, --period STR report using the given period +@c --period-sort EXPR sort each report period's entries by EXPR + +To constrain the report to a specific time period, use +@option{--period} (@option{-p}). A time period may have both a +beginning and an end, or neither, as well as a specified interval. +Here are a few examples: + +@example +ledger -p 2004 bal +ledger -p august bal +ledger -p "from aug to oct" bal +ledger -p "daily from 8/1 to 8/15" bal +ledger -p "weekly since august" bal +ledger -p "monthly from feb to oct" bal +ledger -p "quarterly in 2004" bal +ledger -p yearly bal +@end example + +See @ref{Period expressions} for more on syntax. Also, all of the +options @option{-b}, @option{-e} and @option{-p} may be used together, +but whatever information occurs last takes priority. An example of +such usage (in a script, perhaps) would be: + +@example +ledger -b 2004 -e 2005 -p monthly reg ^expenses +@end example + +This command is identical to: + +@example +ledger -p "monthly in 2004" reg ^expenses +@end example + +The transactions within a period may be sorted using +@option{--period-sort}, which takes a value expression. This is +similar to the @option{--sort} option, except that it sorts within +each period entry, rather than sorting all transactions in the report. +See the documentation on @option{--sort} below for more details. + +@subsubsection By status + +By default, all regular transactions are included in each report. To +limit the report to certain kinds of transactions, use one or more of +the following options: + +@table @option +@item -C, --cleared +Consider only cleared transactions. +@item -U, --uncleared +Consider only uncleared and pending transactions. +@item -R, --real +Consider only real (non-virtual) transactions. +@item -L, --actual +Consider only actual (non-automated) transactions. +@end table + +Cleared transactions are indicated by an asterix placed just before +the payee name in a transaction. The meaning of this flag is up to +the user, but typically it means that an entry has been seen on a +financial statement. Pending transactions use an exclamation mark in +the same position, but are mainly used only by reconciling software. +Uncleared transactions are for things like uncashed checks, credit +charges that haven't appeared on a statement yet, etc. + +Real transactions are all non-virtual transactions, where the account +name is not surrounded by parentheses or square brackets. Virtual +transactions are useful for showing a transfer of money that never +really happened, like money set aside for savings without actually +transferring it from the parent account. + +Actual transactions are those not generated, either as part of an +automated entry, or a budget or forecast report. A useful of when you +might like to filter out generated transactions is with a budget: + +@example +ledger --budget --actual reg ^expenses +@end example + +This command outputs all transactions affecting a budgeted account, +but without subtracting the budget amount (because the generated +transactions are suppressed with @option{--actual}). The report shows +how much you actually spent on budgeted items. + +@subsubsection By relationship + +@c -r, --related calculate report using related transactions + +Normally, a register report includes only the transactions that match +the regular expressions specified after the command word. For +example, to report all expenses: + +@example +ledger reg ^expenses +@end example + +This reports: + +@smallexample +2004/05/29 Book Store Expenses:Books $20.00 $20.00 +@end smallexample + +Using @option{--related} (@option{-r}) reports the transactions that +did not match your query, but only in entries that otherwise would +have matched. This has the effect of indicating where money came +from, or when to: + +@example +ledger -r reg ^expenses +@end example + +Reports: + +@smallexample +2004/05/29 Book Store Liabilities:MasterCard $20.00 $20.00 +@end smallexample + +@subsubsection By budget + +@c --budget generate budget entries based on FILE + +There is more information about budgeting and forecasting in +@ref{Budgeting and forecasting}. Basically, if you have any period +entries in your ledger file, you can use these options. A period +entry looks like: + +@example +~ Monthly + Assets:Bank:Checking $500.00 + Income:Salary +@end example + +The difference from a regular entry is that the first line begins with +a tilde (~), and instead of a payee there's a period expression +(@ref{Period expressions}). Otherwise, a period entry is in every +other way the same as a regular entry. + +With such an entry in your ledger file, the @option{--budget} option +will report only transactions that match a budgeted account. Using +@file{sample.dat} from above: + +@example +ledger --budget reg ^income +@end example + +Reports: + +@smallexample +2004/05/01 Budget entry Income:Salary $500.00 $500.00 +2004/05/14 Pay day Income:Salary $-500.00 0 +@end smallexample + +The final total is zero, indicating that the budget matched exactly +for the reported period. Budgeting is most often helpful with period +reporting; for example, to show monthly budget results use +@option{--budget -p monthly}. + +@c --add-budget show all transactions plus the budget +@c --unbudgeted show only unbudgeted transactions + +The @option{--add-budget} option reports all matching transactions in +addition to budget transactions; while @option{--unbudgeted} shows +only those that don't match a budgeted account. To summarize: + +@table @option +@item --budget +Show transactions matching budgeted accounts. +@item --unbudgeted +Show transactions matching unbudgeted accounts. +@item --add-budget +Show both budgeted and unbudgeted transactions together (i.e., add the +generated budget transactions to the regular report). +@end table + +@c --forecast EXPR generate forecast entries while EXPR is true + +A report with the @option{--forecast} option will add budgeted +transactions while the specified value expression is true. For +example: + +@example +ledger --forecast 'd<[2005] reg ^income +@end example + +Reports: + +@smallexample +2004/05/14 Pay day Income:Salary $-500.00 $-500.00 +2004/12/01 Forecast entry Income:Salary $-500.00 $-1,000.00 +2005/01/01 Forecast entry Income:Salary $-500.00 $-1,500.00 +@end smallexample + +The date this report was made was November 5, 2004; the reason the +first forecast entry is in december is that forecast entries are only +added for the future, and they only stop after the value expression +has matched at least once, which is why the January entry appears. A +forecast report can be very useful for determining when money will run +out in an account, or for projecting future cash flow: + +@example +ledger --forecast 'd<[2008]' -p yearly reg ^inc ^exp +@end example + +This reports balances projected income against projected expenses, +showing the resulting total in yearly intervals until 2008. For the +case of @file{sample.dat}, which has no budgeted expenses, the result +of the above command (in November 2004) is: + +@smallexample +2004/01/01 - 2004/12/31 Income:Salary $-1,000.00 $-1,000.00 + Expenses:Books $20.00 $-980.00 +2005/01/01 - 2005/12/31 Income:Salary $-6,000.00 $-6,980.00 +2006/01/01 - 2006/12/31 Income:Salary $-6,000.00 $-12,980.00 +2007/01/01 - 2007/12/31 Income:Salary $-6,000.00 $-18,980.00 +2008/01/01 - 2008/01/01 Income:Salary $-500.00 $-19,480.00 +@end smallexample + +@subsubsection By value expression + +@c -l, --limit EXPR calculate only transactions matching EXPR + +Value expressions can be quite complex, and are treated more fully in +@ref{Value expressions}. They can be used for limiting a report with +@option{--limit} (@option{-l}). The following command report income +since august, but expenses since october: + +@example +ledger -l '(/income/&d>=[aug])|(/expenses/&d>=[oct])' reg +@end example + +The basic form of this value expression is @samp{(A&B)|(A&B)}. The +@samp{A} in each part matches against an account name with +@samp{/name/}, while each @samp{B} part compares the date of the +transaction (@samp{d}) with a specified month. The resulting report +will contain only transactions which match the value expression. + +@c -t, --amount EXPR use EXPR to calculate the displayed amount +@c -T, --total EXPR use EXPR to calculate the displayed total + +Another use of value expressions is to calculate the amount reported +for each line of a register report, or for computing the subtotal of +each account shown in a balance report. This example divides each +transaction amount by two: + +@example +ledger -t 'a/2' reg ^exp +@end example + +The @option{-t} option doesn't affect the running total, only how the +transaction amount is displayed. To change the running total, use +@option{-T}. In that case, you will likely want to use the total +(@samp{O}) instead of the amount (@samp{a}): + +@example +ledger -T 'O/2' reg ^exp +@end example + +@subsection Massaging register output + +Even after filtering down your data to just the transactions you're +interested in, the default reporting method of one transaction per +line is often still too much. To combat this complexity, it is +possible to ask Ledger to report the details to you in many different +forms, summarized in various ways. This is the ``display'' phase of +Ledger, and is documented under @option{--help-disp}. + +@subsubsection Summarizing + +@c -n, --collapse register: collapse entries with multiple transactions + +When multiple transactions relate to a single entry, they are reported +as part of that entry. For example, in the case of @file{sample.dat}: + +@example +ledger reg -- book +@end example + +Reports: + +@smallexample +2004/05/29 Book Store Expenses:Books $20.00 $20.00 + Liabilities:MasterCard $-20.00 0 + (Liabilities:Taxes) $-2.00 $-2.00 +@end smallexample + +All three transactions are part of one entry, and as such the entry +details are printed only once. To report every entry on a single +line, use @option{-n} to collapse entries with multiple transactions: + +@example +ledger -n reg -- book +@end example + +Reports: + +@smallexample +2004/05/29 Book Store $-2.00 $-2.00 +@end smallexample + +In the balance report, @option{-n} causes the grand total not to be +displayed at the bottom of the report. + +@c -s, --subtotal balance: show sub-accounts; other: show subtotals + +If an account occurs more than once in a report, it is possible to +combine them all and report the total per-account, using @option{-s}. +For example, this command: + +@example +ledger -B reg ^assets +@end example + +Reports: + +@smallexample +2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 +2004/05/01 Investment balance Assets:Brokerage $1,500.00 $2,500.00 +2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 +2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 +@end smallexample + +But if the @option{-s} option is added, the result becomes: + +@smallexample +2004/05/01 - 2004/05/29 Assets:Bank:Checking $1,480.00 $1,480.00 + Assets:Brokerage $1,500.00 $2,980.00 +@end smallexample + +When account subtotaling is used, only one entry is printed, and the +date and name reflect the range of the combined transactions. + +@c -P, --by-payee show summarized totals by payee + +With @option{-P}, transactions relating to the same payee are +combined. In this case, the date of the combined entry is that of the +latest transaction. + +@c -x, --comm-as-payee set commodity name as the payee, for reporting + +@option{-x} changes the payee name for each transaction to be the same +as the commodity it uses. This can be especially useful combined with +other options, like @option{-P}. For example: + +@example +ledger -Px reg ^assets +@end example + +Reports: + +@smallexample +2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 +2004/05/01 AAPL Assets:Brokerage 50 AAPL $1,480.00 + 50 AAPL +@end smallexample + +This reports shows the subtotal for each commodity held, and where it +is located. To see the basis cost, or initial investment, add +@option{-B}. Applied to the example above: + +@smallexample +2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 +2004/05/01 AAPL Assets:Brokerage $1,500.00 $2,980.00 +@end smallexample + +@c -E, --empty balance: show accounts with zero balance + +The only other options which affect summarized totals is @option{-E}, +which works only in the balance report. In this case, it shows +matching accounts with a zero a balance, which are ordinarily +excluded. This can be useful to see all the accounts involved in a +report, even if some have no total. + +@subsubsection Quick periods + +Although the @option{-p} option (also @option{--period}) is much more +versatile, there are other options to make the most common period +reports easier: + +@table @option +@item -W, --weekly +Show weekly sub-totals. Same as @samp{-p weekly}. +@item -M, --monthly +Show monthly sub-totals. Same as @samp{-p monthly}. +@item -Y, --yearly +Show yearly sub-totals. Same as @samp{-p yearly}. +@end table + +@c --dow show a days-of-the-week report + +There is one kind of period report cannot be done with @option{-p}. +This is the @option{--dow}, or ``days of the week'' report, which +shows summarized totals for each day of the week. The following +examples shows a ``day of the week'' report of income and expenses: + +@example +ledger --dow reg ^inc ^exp +@end example + +Reports: + +@smallexample +2004/05/27 Thursdays Expenses:Books $20.00 $20.00 +2004/05/14 Fridays Income:Salary $-500.00 $-480.00 +@end smallexample + +@subsubsection Ordering and width + +@c -S, --sort EXPR sort report according to the value expression EXPR + +The transactions displayed in a report are shown in the same order as +they appear in the ledger file. To change the order and sort a +report, use the @option{--sort} option. @option{--sort} takes a value +expression to determine the value to sort against, making it possible +to sort according to complex criteria. Here are some simple and +useful examples: + +@example +ledger --sort d reg ^exp # sort by date +ledger --sort t reg ^exp # sort by amount total +ledger --sort -t reg ^exp # reverse sort by amount total +ledger --sort Ut reg ^exp # sort by abs amount total +@end example + +For the balance report, you will want to use @samp{T} instead of +@samp{t}: + +@example +ledger --sort T reg ^exp # sort by amount total +ledger --sort -T reg ^exp # reverse sort by amount total +ledger --sort UT reg ^exp # sort by abs amount total +@end example + +The @option{--sort} options sorts all transactions in a report. If +periods are used (such as @option{--monthly}), this can get somewhat +confusing. In that case, you'll probably want to sort within periods +using @option{--period-sort} instead of @option{--sort}. + +@c -w, --wide for the default register report, use 132 columns + +And if the register seems too cramped, and you have a lot of screen +real estate, you can use @option{-w} to format the report within 132 +acolumns, instead of 80. You are more likely then to see full payee +and account names, as well as properly formatted totals when +long-named commodities are used. + +If you want only the first or last N entries to be printed---which can +be very useful for viewing the last 10 entries in your checking +account, while also showing the cumulative balance from all +entries---use the @option{--head} and/or @option{--tail} options. The +two options may be used simultaneously, for example: + +@example +ledger --tail 20 reg checking +@end example + +If the output from your command is very long, Ledger can output the +data to a pager utility, such as @command{more} or @command{less}: + +@example +ledger --pager /usr/bin/less reg checking +@end example + +@subsubsection Averages and percentages + +@c -A, --average report average transaction amount + +To see the running total changed to a running average, use +@option{-A}. The final transaction's total will be the overall +average of all displayed transactions. The works in conjunction with +period reporting, so that you can see your monthly average expenses +with: + +@example +ledger -AM reg ^expenses:food +ledger -AMn reg ^expenses +@end example + +This works in the balance report too: + +@example +ledger -AM bal ^expenses:food +ledger -AMs bal ^expenses +@end example + +@c -D, --deviation report deviation from the average + +The @option{-D} option changes the running average into a deviation +from the running average. This only makes sense in the register +report, however. + +@example +ledger -DM reg ^expenses:food +@end example + +@c -%, --percentage report balance totals as a percentile of the parent + +In the balance report only, @option{-%} changes the reported totals +into a percentage of the parent account. This kind of report is +confusing if negative amounts are involved, and doesn't work at all if +multiple commodities occur in an account's history. It has a somewhat +limited usefulness, therefore, but in certain cases it can be handy, +such as reviewing overall expenses: + +@example +ledger -%s -S T bal ^expenses +@end example + +@subsubsection Reporting total data + +@c --totals in the "xml" report, include running total + +Normally in the @command{xml} report, only transaction amounts are +printed. To include the running total under a @samp{} tag, use +@option{--totals}. This does not affect any other report. + +@c -j, --amount-data print only raw amount data (useful for scripting) +@c -J, --total-data print only raw total data + +In the register report only, the output can be changed with +@option{-j} to show only the date and the amount---without +commodities. This only makes sense if a single commodity appears in +the report, but can be quite useful for scripting, or passing the data +to Gnuplot. To show only the date and running total, use @option{-J}. + +@subsubsection Display by value expression + +@c -d, --display EXPR display only transactions matching EXPR + +With @option{-d} you can decide which transactions (or accounts in the +balance report) are displayed, according to a value expression. The +computed total is not affected, only the display. This can be very +useful for shortening a report without changing the running total: + +@example +ledger -d 'd>=[last month]' reg checking +@end example + +This command shows the checking account's register, beginning from +last month, but with the running total reflecting the entire history +of the account. + +@subsubsection Change report format + +@c -y, --date-format STR use STR as the date format (default: %Y/%m/%d) + +When dates are printed in any report, the default format is +@samp{%Y/%m/%d}, which yields dates of the form @samp{YYYY/mm/dd}. +This can be changed with @option{-y}, whose argument is a +@code{strftime} string---see your system's C library documentation for +the allowable codes. Mostly you will want to use @samp{%Y}, @samp{%m} +and @samp{%d}, in whatever combination is convenient for your locale. + +@c -F, --format STR use STR as the format; for each report type, use: +@c --balance-format --register-format --print-format +@c --plot-amount-format --plot-total-format --equity-format +@c --prices-format --wide-register-format + +To change the format of the entire reported line, use @option{-F}. It +supports quite a large number of options, which are all documented in +@ref{Format strings}. In addition, each specific kind of report +(except for @command{xml}) can be changed using one of the following +options: + +@table @option +@item --balance-format +@command{balance} report. Default: +@smallexample +%20T %2_%-a\n +@end smallexample + +@item --register-format +@command{register} report. Default: +@smallexample +%D %-.20P %-.22A %12.66t %12.80T\n%/%32|%-.22A %12.66t %12.80T\n +@end smallexample + +@item --print-format +@command{print} report. Default: +@smallexample +%D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n +@end smallexample + +@item --plot-amount-format +@command{register} report when @option{-j} (plot amount) is used. Default: +@smallexample +%D %(St)\n +@end smallexample + +@item --plot-total-format +@command{register} report when @option{-J} (plot total) is used. Default: +@smallexample +%D %(ST)\n +@end smallexample + +@item --equity-format +@command{equity} report. Default: +@smallexample +\n%D %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n +@end smallexample + +@item --prices-format +@command{prices} report. Default: +@smallexample +\n%D %Y%C%P\n%/ %-34W %12t\n +@end smallexample + +@item --wide-register-format +@command{register} report when @option{-w} (wide) is used. Default: +@smallexample +%D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n +@end smallexample +@end table + +@subsection Standard queries + +If your ledger file uses the standard top-level accounts: Assets, +Liabilities, Income, Expenses, Equity: then the following queries will +enable you to generate some typical accounting reports from your data. + +Your @emph{net worth} can be determined by balancing assets against +liabilities: + +@example +ledger bal ^assets ^liab +@end example + +By removing long-term investment and loan accounts, you can see your +current net liquidity (or liquid net worth): + +@example +ledger bal ^assets ^liab -retirement -brokerage -loan +@end example + +Balancing expenses against income yields your @emph{cash flow}, or net +profit/loss: + +@example +ledger bal ^exp ^inc +@end example + +In this case, if the number is positive it means you spent more than +you earned during the report period. + +@c ---------------------------------------------------------------------- + +The most often used command is the ``balance'' command: + +@example +export LEDGER=/home/johnw/doc/ledger.dat +ledger balance +@end example + +Here I've set my Ledger environment variable to point to where my +ledger file is hiding. Thereafter, I needn't specify it again. + +@subsection Reporting balance totals + +The balance command prints out the summarized balances of all my +top-level accounts, excluding sub-accounts. In order to see the +balances for a specific account, just specify a regular expression +after the balance command: + +@example +ledger balance expenses:food +@end example + +This will show all the money that's been spent on food, since the +beginning of the ledger. For food spending just this month +(September), use: + +@example +ledger -p sep balance expenses:food +@end example + +Or maybe you want to see all of your assets, in which case the -s +(show sub-accounts) option comes in handy: + +@example +ledger -s balance ^assets +@end example + +To exclude a particular account, use a regular expression with a +leading minus sign. The following will show all expenses, but without +food spending: + +@example +ledger balance expenses -food +@end example + +@subsection Reporting percentages + +There is no built-in way to report transaction amounts or account +balances in terms of percentages + +@node Commands, Options, Usage overview, Running Ledger +@section Commands + +@subsection balance + +The @command{balance} command reports the current balance of all +accounts. It accepts a list of optional regexps, which confine the +balance report to the matching accounts. If an account contains +multiple types of commodities, each commodity's total is reported +separately. + +@subsection register + +The @command{register} command displays all the transactions occurring +in a single account, line by line. The account regexp must be +specified as the only argument to this command. If any regexps occur +after the required account name, the register will contain only those +transactions that match. Very useful for hunting down a particular +transaction. + +The output from @command{register} is very close to what a typical +checkbook, or single-account ledger, would look like. It also shows a +running balance. The final running balance of any register should +always be the same as the current balance of that account. + +If you have Gnuplot installed, you may plot the amount or running +total of any register by using the script @file{report}, which is +included in the Ledger distribution. The only requirement is that you +add either @option{-j} or @option{-J} to your register command, in +order to plot either the amount or total column, respectively. + +@subsection print + +The @command{print} command prints out ledger entries in a textual +format that can be parsed by Ledger. They will be properly formatted, +and output in the most economic form possible. The ``print'' command +also takes a list of optional regexps, which will cause only those +transactions which match in some way to be printed. + +The @command{print} command can be a handy way to clean up a ledger +file whose formatting has gotten out of hand. + +@subsection output + +The @command{output} command is very similar to the @command{print} +command, except that it attempts to replicate the specified ledger +file exactly. The format of the command is: + +@example +ledger -f FILENAME output FILENAME +@end example + +Where @file{FILENAME} is the name of the ledger file to output. The +reason for specifying this command is that only entries contained +within that file will be output, and not an included entries (as can +happen with the @command{print} command). + +@subsection xml + +The @command{xml} command outputs results similar to what +@command{print} and @command{register} display, but as an XML form. +This data can then be read in and processed. Use the +@option{--totals} option to include the running total with each +transaction. + +@subsection emacs + +The @command{emacs} command outputs results in a form that can be read +directly by Emacs Lisp. The format of the sexp is: + +@example +((BEG-POS CLEARED DATE CODE PAYEE + (ACCOUNT AMOUNT)...) ; list of transactions + ...) ; list of entries +@end example + +@subsection equity + +The @command{equity} command prints out accounts balances as if they +were entries. This makes it easy to establish the starting balances +for an account, such as when @ref{Archiving previous years}. + +@subsection prices + +The @command{prices} command displays the price history for matching +commodities. The @option{-A} flag is useful with this report, to +display the running average price, or @option{-D} to show each price's +deviation from that average. + +There is also a @command{pricesdb} command which outputs the same +information as @command{prices}, but does in a format that can be +parsed by Ledger. + +@subsection entry + +The @command{entry} commands simplifies the creation of new entries. +It works on the principle that 80% of all transactions are variants of +earlier transactions. Here's how it works: + +Say you currently have this transaction in your ledger file: + +@smallexample +2004/03/15 * Viva Italiano + Expenses:Food $12.45 + Expenses:Tips $2.55 + Liabilities:MasterCard $-15.00 +@end smallexample + +Now it's @samp{2004/4/9}, and you've just eating at @samp{Viva +Italiano} again. The exact amounts are different, but the overall +form is the same. With the @command{entry} command you can type: + +@example +ledger entry 2004/4/9 viva food 11 tips 2.50 +@end example + +This produces the following output: + +@smallexample +2004/04/09 Viva Italiano + Expenses:Food $11.00 + Expenses:Tips $2.50 + Liabilities:MasterCard $-13.50 +@end smallexample + +It works by finding a past transaction matching the regular expression +@samp{viva}, and assuming that any accounts or amounts specified will +be similar to that earlier transaction. If Ledger does not succeed in +generating a new entry, an error is printed and the exit code is set +to @samp{1}. + +There is a shell script in the distribution's @file{scripts} directory +called @file{entry}, which simplifies the task of adding a new entry +to your ledger. It launches @command{vi} to confirm that the entry +looks appropriate. + +Here are a few more examples of the @command{entry} command, assuming +the above journal entry: + +@example +ledger entry 4/9 viva 11.50 +ledger entry 4/9 viva 11.50 checking # (from `checking') +ledger entry 4/9 viva food 11.50 tips 8 +ledger entry 4/9 viva food 11.50 tips 8 cash +ledger entry 4/9 viva food $11.50 tips $8 cash +ledger entry 4/9 viva dining "DM 11.50" +@end example + +@node Options, Format strings, Commands, Running Ledger +@section Options + +With all of the reports, command-line options are useful to modify the +output generated. These command-line options always occur before the +command word. This is done to distinguish options from exclusive +regular expressions, which also begin with a dash. The basic form for +most commands is: + +@example +ledger [OPTIONS] COMMAND [REGEXPS...] [-- [REGEXPS...]] +@end example + +The @var{OPTIONS} and @var{REGEXPS} expressions are both optional. +You could just use @samp{ledger balance}, without any options---which +prints a summary of all accounts. But for more specific reporting, or +to change the appearance of the output, options are needed. + +@menu +* Basic options:: +* Report filtering:: +* Output customization:: +* Commodity reporting:: +* Environment variables:: +@end menu + +@node Basic options, Report filtering, Options, Options +@subsection Basic options + +These are the most basic command options. Most likely, the user will +want to set them using @ref{Environment variables}, instead of using +actual command-line options: + +@option{--help} (@option{-h}) prints a summary of all the options, and +what they are used for. This can be a handy way to remember which +options do what. This help screen is also printed if ledger is run +without a command. + +@option{--version} (@option{-v}) prints the current version of ledger +and exits. This is useful for sending bug reports, to let the author +know which version of ledger you are using. + +@option{--file FILE} (@option{-f FILE}) reads FILE as a ledger file. +This command may be used multiple times. FILE may also be a list of +file names separated by colons. Typically, the environment variable +@env{LEDGER_FILE} is set, rather than using this command-line option. + +@option{--output FILE} (@option{-o FILE}) redirects output from any +command to @var{FILE}. By default, all output goes to standard +output. + +@option{--init-file FILE} (@option{-i FILE}) causes FILE to be read by +ledger before any other ledger file. This file may not contain any +transactions, but it may contain option settings. To specify options +in the init file, use the same syntax as the command-line. Here's an +example init file: + +@smallexample +--price-db ~/finance/.pricedb + +; ~/.ledgerrc ends here +@end smallexample + +Option settings on the command-line or in the environment always take +precedence over settings in the init file. + +@option{--cache FILE} identifies FILE as the default binary cache +file. That is, if the ledger files to be read are specified using the +environment variable @env{LEDGER_FILE}, then whenever a command is +finished a binary copy will be written to the specified cache, to +speed up the loading time of subsequent queries. This filename can +also be given using the environment variable @env{LEDGER_CACHE}, or by +putting the option into your init file. The @option{--no-cache} +option causes Ledger to always ignore the binary cache. + +@option{--account NAME} (@option{-a NAME}) specifies the default +account which QIF file transactions are assumed to relate to. + +@node Report filtering, Output customization, Basic options, Options +@subsection Report filtering + +These options change which transactions affect the outcome of a +report, in ways other than just using regular expressions: + +@option{--current}(@option{-c}) displays only entries occurring on or +before the current date. + +@option{--begin DATE} (@option{-b DATE}) constrains the report to +entries on or after @var{DATE}. Only entries after that date will be +calculated, which means that the running total in the balance report +will always start at zero with the first matching entry. (Note: This +is different from using @option{--display} to constrain what is +displayed). + +@option{--end DATE} (@option{-e DATE}) constrains the report so that +entries on or after @var{DATE} are not considered. The ending date +is inclusive. + +@option{--period STR} (@option{-p STR}) sets the reporting period +to @var{STR}. This will subtotal all matching entries within each +period separately, making it easy to see weekly, monthly, quarterly, +etc., transaction totals. A period string can even specify the +beginning and end of the report range, using simple terms like ``last +june'' or ``next month''. For more using period expressions, see +@ref{Period expressions}. + +@option{--period-sort EXPR} sorts the transactions within each +reporting period using the value expression @var{EXPR}. This is most +often useful when reporting monthly expenses, in order to view the +highest expense categories at the top of each month: + +@example +ledger -M --period-sort -At reg ^Expenses +@end example + +@option{--cleared} (@option{-C}) displays only transactions whose entry +has been marked ``cleared'' (by placing an asterix to the right of the +date). + +@option{--uncleared} (@option{-U}) displays only transactions whose +entry has not been marked ``cleared'' (i.e., if there is no asterix to +the right of the date). + +@option{--real} (@option{-R}) displays only real transactions, not +virtual. (A virtual transaction is indicated by surrounding the +account name with parentheses or brackets; see the section on using +virtual transactions for more information). + +@option{--actual} (@option{-L}) displays only actual transactions, and +not those created due to automated transactions. + +@option{--related} (@option{-r}) displays transactions that are +related to whichever transactions would otherwise have matched the +filtering criteria. In the register report, this shows where money +went to, or the account it came from. In the balance report, it shows +all the accounts affected by entries having a related transaction. +For example, if a file had this entry: + +@smallexample +2004/03/20 Safeway + Expenses:Food $65.00 + Expenses:Cash $20.00 + Assets:Checking $-85.00 +@end smallexample + +And the register command was: + +@example +ledger -r register food +@end example + +The following would be output, showing the transactions related to the +transaction that matched: + +@smallexample +2004/03/20 Safeway Expenses:Cash $-20.00 $-20.00 + Assets:Checking $85.00 $65.00 +@end smallexample + +@option{--budget} is useful for displaying how close your transactions +meet your budget. @option{--add-budget} also shows unbudgeted +transactions, while @option{--unbudgeted} shows only those. +@option{--forecast} is a related option that projects your budget into +the future, showing how it will affect future balances. +@xref{Budgeting and forecasting}. + +@option{--limit EXPR} (@option{-l EXPR}) limits which transactions +take part in the calculations of a report. + +@option{--amount EXPR} (@option{-t EXPR}) changes the value expression +used to calculate the ``value'' column in the @command{register} +report, the amount used to calculate account totals in the +@command{balance} report, and the values printed in the +@command{equity} report. @xref{Value expressions}. + +@option{--total EXPR} (@option{-T EXPR}) sets the value expression +used for the ``totals'' column in the @command{register} and +@command{balance} reports. + +@node Output customization, Commodity reporting, Report filtering, Options +@subsection Output customization + +These options affect only the output, but not which transactions are +used to create it: + +@option{--collapse} (@option{-n}) causes entries in a +@command{register} report with multiple transactions to be collapsed +into a single, subtotaled entry. + +@option{--subtotal} (@option{-s}) causes all entries in a +@command{register} report to be collapsed into a single, subtotaled +entry. + +@option{--by-payee} (@option{-P}) reports subtotals by payee. + +@option{--comm-as-payee} (@option{-x}) changes the payee of every +transaction to be the commodity used in that transaction. This can be +useful when combined with other options, such as @option{-s}. + +@option{--empty} (@option{-E}) includes even empty accounts in the +@command{balance} report. + +@option{--weekly} (@option{-W}) reports transaction totals by the +week. The week begins on whichever day of the week begins the month +containing that transaction. To set a specific begin date, use a +period string, such as @samp{weekly from DATE}. @option{--monthly} +(@option{-M}) reports transaction totals by month; @option{--yearly} +(@option{-Y}) reports transaction totals by year. For more complex +period, using the @option{--period} option described above. + +@option{--dow} reports transactions totals for each day of the week. +This is an easy way to see if weekend spending is more than on +weekdays. + +@option{--sort EXPR} (@option{-S EXPR}) sorts a report by comparing +the values determined using the value expression @var{EXPR}. For +example, using @option{-S -UT} in the balance report will sort account +balances from greatest to least, using the absolute value of the +total. For more on how to use value expressions, see @ref{Value +expressions}. + +@option{--wide} (@option{-w}) causes the default @command{register} +report to assume 132 columns instead of 80. + +@option{--head} causes only the first N entries to be printed. This +is different from using the command-line utility @command{head}, which +would limit to the first N transactions. @option{--tail} outputs only +the last N entries. Both options may be used simultaneously. If a +negative amount is given, it will invert the meaning of the flag +(instead of the first five entries being printed, for example, it +would print all but the first five). + +@option{--pager} tells Ledger to pass its output to the given pager +program---very useful when the output is especially long. This +behavior can be made the default by setting the @env{LEDGER_PAGER} +environment variable. + +@option{--average} (@option{-A}) reports the average transaction +value. + +@option{--deviation} (@option{-D}) reports each transaction's +deviation from the average. It is only meaningful in the +@command{register} and @command{prices} reports. + +@option{--percentage} (@option{-%}) shows account subtotals in the +@command{balance} report as percentages of the parent account. + +@option{--totals} include running total information in the +@command{xml} report. + +@option{--amount-data} (@option{-j}) changes the @command{register} +report so that it output nothing but the date and the value column, +and the latter without commodities. This is only meaningful if the +report uses a single commodity. This data can then be fed to other +programs, which could plot the date, analyze it, etc. + +@option{--total-data} (@option{-J}) changes the @command{register} +report so that it output nothing but the date and totals column, +without commodities. + +@option{--display EXPR} (@option{-d EXPR}) limits which transactions +or accounts or actually displayed in a report. They might still be +calculated, and be part of the running total of a register report, for +example, but they will not be displayed. This is useful for seeing +last month's checking transactions, against a running balance which +includes all transaction values: + +@example +ledger -d "d>=[last month]" reg checking +@end example + +The output from this command is very different from the following, +whose running total includes only transactions from the last month +onward: + +@example +ledger -p "last month" reg checking +@end example + +Which is more useful depends on what you're looking to know: the total +amount for the reporting range (@option{-p}), or simply a display +restricted to the reporting range (using @option{-d}). + +@option{--date-format STR} (@option{-y STR}) changes the basic date +format used by reports. The default uses a date like 2004/08/01, +which represents the default date format of @samp{%Y/%m/%d}. To +change the way dates are printed in general, the easiest way is to put +@option{--date-format FORMAT} in the Ledger initialization file +@file{~/.ledgerrc} (or the file referred to by @env{LEDGER_INIT}). + +@option{--format STR} (@option{-F STR}) sets the reporting format for +whatever report ledger is about to make. @xref{Format strings}. +There are also specific format commands for each report type: + +@itemize +@item @option{--balance-format STR} +@item @option{--register-format STR} +@item @option{--print-format STR} +@item @option{--plot-amount-format STR} (-j @command{register}) +@item @option{--plot-total-format STR} (-J @command{register}) +@item @option{--equity-format STR} +@item @option{--prices-format STR} +@item @option{--wide-register-format STR} (-w @command{register}) +@end itemize + +@node Commodity reporting, Environment variables, Output customization, Options +@subsection Commodity reporting + +These options affect how commodity values are displayed: + +@option{--price-db FILE} sets the file that is used for recording +downloaded commodity prices. It is always read on startup, to +determine historical prices. Other settings can be placed in this +file manually, to prevent downloading quotes for a specific, for +example. This is done by adding a line like the following: + +@example +; Don't download quotes for the dollar, or timelog values +N $ +N h +@end example + +@option{--price-exp MINS} (@option{-L MINS}) sets the expected +freshness of price quotes, in minutes. That is, if the last known +quote for any commodity is older than this value---and if +@option{--download} is being used---then the Internet will be +consulted again for a newer price. Otherwise, the old price is still +considered to be fresh enough. + +@option{--download} (@option{-Q}) causes quotes to be automagically +downloaded, as needed, by running a script named @command{getquote} +and expecting that script to return a value understood by ledger. A +sample implementation of a @command{getquote} script, implemented in +Perl, is provided in the distribution. Downloaded quote price are +then appended to the price database, usually specified using the +environment variable @env{LEDGER_PRICE_DB}. + +There are several different ways that ledger can report the totals it +displays. The most flexible way to adjust them is by using value +expressions, and the @option{-t} and @option{-T} options. However, +there are also several ``default'' reports, which will satisfy most +users basic reporting needs: + +@table @code +@item -O, --quantity +Reports commodity totals (this is the default) + +@item -B, --basis +Reports the cost basis for all transactions. + +@item -V, --market +Reports the last known market value for all commodities. + +@item -g, --performance +Reports the net gain/loss for each transaction in a @command{register} +report. + +@item -G --gain +Reports the net gain/loss for all commodities in the report that have +a price history. +@end table + +@node Environment variables, , Commodity reporting, Options +@subsection Environment variables + +Every option to ledger may be set using an environment variable. If +an option has a long name such @option{--this-option}, setting the +environment variable @env{LEDGER_THIS_OPTION} will have the same +affect as specifying that option on the command-line. Options on the +command-line always take precedence over environment variable +settings, however. + +Note that you may also permanently specify option values by placing +option settings in the file @file{~/.ledgerrc}, for example: + +@example +--cache /tmp/.mycache +@end example + +@node Format strings, Value expressions, Options, Running Ledger +@section Format strings + +Format strings may be used to change the output format of reports. +They are specified by passing a formatting string to the +@option{--format} (@option{-F}) option. Within that string, +constructs are allowed which make it possible to display the various +parts of an account or transaction in custom ways. + +Within a format strings, a substitution is specified using a percent +character (@samp{%}). The basic format of all substitutions is: + +@example +%[-][MIN WIDTH][.MAX WIDTH]EXPR +@end example + +If the optional minus sign (@samp{-}) follows the percent character, +whatever is substituted will be left justified. The default is right +justified. If a minimum width is given next, the substituted text +will be at least that wide, perhaps wider. If a period and a maximum +width is given, the substituted text will never be wider than this, +and will be truncated to fit. Here are some examples: + +@example +%-P An entry's payee, left justified +%20P The same, right justified, at least 20 chars wide +%.20P The same, no more than 20 chars wide +%-.20P Left justified, maximum twenty chars wide +@end example + +The expression following the format constraints can be a single +letter, or an expression enclosed in parentheses or brackets. The +allowable expressions are: + +@table @code +@item % +Inserts a percent sign. + +@item t +Inserts the results of the value expression specified by @option{-t}. +If @option{-t} was not specified, the current report style's value +expression is used. + +@item T +Inserts the results of the value expression specified by @option{-T}. +If @option{-T} was not specified, the current report style's value +expression is used. + +@item | +Inserts a single space. This is useful if a width is specified, for +inserting a certain number of spaces. + +@item _ +Inserts a space for each level of an account's depth. That is, if an +account has two parents, this construct will insert two spaces. If a +minimum width is specified, that much space is inserted for each level +of depth. Thus @samp{%5_}, for an account with four parents, will +insert twenty spaces. + +@item (EXPR) +Inserts the amount resulting from the value expression given in +parentheses. To insert five times the total value of an account, for +example, one could say @samp{%12(5*O)}. Note: It's important to put +the five first in that expression, so that the commodity doesn't get +stripped from the total. + +@item [DATEFMT] +Inserts the result of formatting a transaction's date with a date +format string, exactly like those supported by @code{strftime}. For +example: @samp{%[%Y/%m/%d %H:%M:%S]}. + +@item S +Insert the pathname of the file from which the entry's data was read. + +@item B +Inserts the beginning character position of that entry within the file. + +@item b +Inserts the beginning line of that entry within the file. + +@item E +Inserts the ending character position of that entry within the file. + +@item e +Inserts the ending line of that entry within the file. + +@item D +By default, this is the same as @samp{%[%Y/%m%/d]}. The date format +used can be changed at any time with the @option{-y} flag, however. +Using @samp{%D} gives the user more control over the way dates are +output. + +@item d +This is the same as the @samp{%D} option, unless the entry has an +effective date, in which case it prints +@samp{[ACTUAL_DATE=EFFECtIVE_DATE]}. + +@item X +If a transaction has been cleared, this inserts @samp{*} followed by a +space; otherwise nothing is inserted. + +@item Y +This is the same as @samp{%X}, except that it only displays a state +character if all of the member transactions have the same state. + +@item C +Inserts the checking number for an entry, in parentheses, followed by +a space; if none was specified, nothing is inserted. + +@item P +Inserts the payee related to a transaction. + +@item a +Inserts the optimal short name for an account. This is normally used +in balance reports. It prints a parent account's name if that name +has not been printed yet, otherwise it just prints the account's name. + +@item A +Inserts the full name of an account. + +@item W +This is the same as @samp{%A}, except that it first displays the +transaction's state @emph{if the entry's transaction states are not +all the same}, followed by the full account name. This is offered as +a printing optimization, so that combined with @samp{%Y}, only the +minimum amount of state detail is printed. + +@item o +Inserts the ``optimized'' form of a transaction's amount. This is +used by the print report. In some cases, this inserts nothing; in +others, it inserts the transaction amount and its cost. It's use is +not recommend unless you are modifying the print report. + +@item n +Inserts the note associated with a transaction, preceded by two spaces +and a semi-colon, if it exists. Thus, no none becomes an empty +string, while the note @samp{foo} is substituted as @samp{ ; foo}. + +@item N +Inserts the note associated with a transaction, if one exists. + +@item / +The @samp{%/} construct is special. It separates a format string +between what is printed for the first transaction of an entry, and +what is printed for all subsequent transactions. If not used, the +same format string is used for all transactions. +@end table + +@node Value expressions, Period expressions, Format strings, Running Ledger +@section Value expressions + +Value expressions are an expression language used by Ledger to +calculate values used by the program for many different purposes: + +@enumerate +@item +The values displayed in reports +@item +For predicates (where truth is anything non-zero), to determine which +transactions are calculated (@option{-l}) or displayed (@option{-d}). +@item +For sorting criteria, to yield the sort key. +@item +In the matching criteria used by automated transactions. +@end enumerate + +Value expressions support most simple math and logic operators, in +addition to a set of one letter functions and variables. A function's +argument is whatever follows it. The following is a display predicate +that I use with the @command{balance} command: + +@example +ledger -d /^Liabilities/?T<0:UT>100 balance +@end example + +The effect is that account totals are displayed only if: 1) A +Liabilities account has a total less than zero; or 2) the absolute +value of the account's total exceeds 100 units of whatever commodity +contains. If it contains multiple commodities, only one of them must +exceed 100 units. + +Display predicates are also very handy with register reports, to +constrain which entries are printed. For example, the following +command shows only entries from the beginning of the current month, +while still calculating the running balance based on all entries: + +@example +ledger -d "d>[this month]" register checking +@end example + +This advantage to this command's complexity is that it prints the +running total in terms of all entries in the register. The following, +simpler command is similar, but totals only the displayed +transactions: + +@example +ledger -b "this month" register checking +@end example + +@subsection Variables + +Below are the one letter variables available in any value expression. +For the register and print commands, these variables relate to +individual transactions, and sometimes the account affected by a +transaction. For the balance command, these variables relate to +accounts---often with a subtle difference in meaning. The use of each +variable for both is specified. + +@table @code +@item t +This maps to whatever the user specified with @option{-t}. In a +register report, @option{-t} changes the value column; in a balance +report, it has no meaning by default. If @option{-t} was not +specified, the current report style's value expression is used. + +@item T +This maps to whatever the user specified with @option{-T}. In a +register report, @option{-T} changes the totals column; in a balance +report, this is the value given for each account. If @option{-T} was +not specified, the current report style's value expression is used. + +@item m +This is always the present moment/date. +@end table + +@subsubsection Transaction/account details + +@table @code +@item d +A transaction's date, as the number of seconds past the epoch. This +is always ``today'' for an account. + +@item a +The transaction's amount; the balance of an account, without +considering children. + +@item b +The cost of a transaction; the cost of an account, without its +children. + +@item v +The market value of a transaction, or an account without its children. + +@item g +The net gain (market value minus cost basis), for a transaction or an +account without its children. It is the same as @samp{v-b}. + +@item l +The depth (``level'') of an account. If an account has one parent, +it's depth is one. + +@item n +The index of a transaction, or the count of transactions affecting an +account. + +@item X +1 if a transaction's entry has been cleared, 0 otherwise. + +@item R +1 if a transaction is not virtual, 0 otherwise. + +@item Z +1 if a transaction is not automated, 0 otherwise. +@end table + +@subsubsection Calculated totals + +@table @code +@item O +The total of all transactions seen so far, or the total of an account +and all its children. + +@item N +The total count of transactions affecting an account and all its +children. + +@item B +The total cost of all transactions seen so far; the total cost of an +account and all its children. + +@item V +The market value of all transactions seen so far, or of an account and +all its children. + +@item G +The total net gain (market value minus cost basis), for a series of +transactions, or an account and its children. It is the same as +@samp{V-B}. +@end table + +@subsection Functions + +The available one letter functions are: + +@table @code +@item - +Negates the argument. + +@item U +The absolute (unsigned) value of the argument. + +@item S +Strips the commodity from the argument. + +@item A +The arithmetic mean of the argument; @samp{Ax} is the same as +@samp{x/n}. + +@item P +The present market value of the argument. The syntax @samp{P(x,d)} is +supported, which yields the market value at time @samp{d}. If no date +is given, then the current moment is used. +@end table + +@subsection Operators + +The binary and ternary operators, in order of precedence, are: + +@enumerate +@item @samp{* /} +@item @samp{+ -} +@item @samp{! < > =} +@item @samp{& | ?:} +@end enumerate + +@subsection Complex expressions + +More complicated expressions are possible using: + +@table @code +@item NUM +A plain integer represents a commodity-less amount. + +@item @{AMOUNT@} +An amount in braces can be any kind of amount supported by ledger, +with or without a commodity. Use this for decimal values. + +@item /REGEXP/ +@item W/REGEXP/ +A regular expression that matches against an account's full name. If +a transaction, this will match against the account affected by the +transaction. + +@item //REGEXP/ +@item p/REGEXP/ +A regular expression that matches against an entry's payee name. + +@item ///REGEXP/ +@item w/REGEXP/ +A regular expression that matches against an account's base name. If +a transaction, this will match against the account affected by the +transaction. + +@item c/REGEXP/ +A regular expression that matches against the entry code (the text +that occurs between parentheses before the payee name). + +@item e/REGEXP/ +A regular expression that matches against a transaction's note, or +comment field. + +@item (EXPR) +A sub-expression is nested in parenthesis. This can be useful passing +more complicated arguments to functions, or for overriding the natural +precedence order of operators. + +@item [DATE] +Useful specifying a date in plain terms. For example, you could say +@samp{[2004/06/01]}. +@end table + +@node Period expressions, File format, Value expressions, Running Ledger +@section Period expressions + +A period expression indicates a span of time, or a reporting interval, +or both. The full syntax is: + +@example +[INTERVAL] [BEGIN] [END] +@end example + +The optional @var{INTERVAL} part may be any one of: + +@example +every day +every week +every monthly +every quarter +every year +every N days # N is any integer +every N weeks +every N months +every N quarters +every N years +daily +weekly +biweekly +monthly +bimonthly +quarterly +yearly +@end example + +After the interval, a begin time, end time, both or neither may be +specified. As for the begin time, it can be either of: + +@example +from +since +@end example + +The end time can be either of: + +@example +to +until +@end example + +Where @var{SPEC} can be any of: + +@example +2004 +2004/10 +2004/10/1 +10/1 +october +oct +this week # or day, month, quarter, year +next week +last week +@end example + +The beginning and ending can be given at the same time, if it spans a +single period. In that case, just use @var{SPEC} by itself. In that +case, the period @samp{oct}, for example, will cover all the days in +october. The possible forms are: + +@example + +in +@end example + +Here are a few examples of period expressions: + +@example +monthly +monthly in 2004 +weekly from oct +weekly from last month +from sep to oct +from 10/1 to 10/5 +monthly until 2005 +from apr +until nov +last oct +weekly last august +@end example + +@node File format, Some typical queries, Period expressions, Running Ledger +@section File format + +The ledger file format is quite simple, but also very flexible. It +supports many options, though typically the user can ignore most of +them. They are summarized below. + +The initial character of each line determines what the line means, and +how it should be interpreted. Allowable initial characters are: + +@table @code +@item NUMBER +A line beginning with a number denotes an entry. It may be followed +by any number of lines, each beginning with whitespace, to denote the +entry's account transactions. The format of the first line is: + +@example +DATE[=EDATE] [*|!] [(CODE)] DESC +@end example + +If @samp{*} appears after the date (with optional effective date), it +indicates the entry is ``cleared'', which can mean whatever the user +wants it t omean. If @samp{!} appears after the date, it indicates d +the entry is ``pending''; i.e., tentatively cleared from the user's +point of view, but not yet actually cleared. If a @samp{CODE} appears +in parentheses, it may be used to indicate a check number, or the type +of the transaction. Following these is the payee, or a description of +the transaction. + +The format of each following transaction is: + +@example + ACCOUNT AMOUNT [; NOTE] +@end example + +The @samp{ACCOUNT} may be surrounded by parentheses if it is a virtual +transactions, or square brackets if it is a virtual transactions that +must balance. The @samp{AMOUNT} can be followed by a per-unit +transaction cost, by specifying @samp{@ AMOUNT}, or a complete +transaction cost with @samp{@@ AMOUNT}. Lastly, the @samp{NOTE} may +specify an actual and/or effective date for the transaction by using +the syntax @samp{[ACTUAL_DATE]} or @samp{[=EFFECTIVE_DATE]} or +@samp{[ACTUAL_DATE=EFFECtIVE_DATE]}. + +@item = +An automated entry. A value expression must appear after the equal +sign. + +After this initial line there should be a set of one or more +transactions, just as if it were normal entry. If the amounts of the +transactions have no commodity, they will be applied as modifiers to +whichever real transaction is matched by the value expression. + +@item ~ +A period entry. A period expression must appear after the tilde. + +After this initial line there should be a set of one or more +transactions, just as if it were normal entry. + +@item ! +A line beginning with an exclamation mark denotes a command directive. +It must be immediately followed by the command word. The supported +commands are: + +@table @samp +@item !include +Include the stated ledger file. + +@item !account +The account name is given is taken to be the parent of all +transactions that follow, until @samp{!end} is seen. + +@item !end +Ends an account block. +@end table + +@item ; +A line beginning with a colon indicates a comment, and is ignored. + +@item Y +If a line begins with a capital Y, it denotes the year used for all +subsequent entries that give a date without a year. The year should +appear immediately after the Y, for example: @samp{Y2004}. This is +useful at the beginning of a file, to specify the year for that file. +If all entries specify a year, however, this command has no effect. + +@item P +Specifies a historical price for a commodity. These are usually found +in a pricing history file (see the @option{-Q} option). The syntax +is: +@example +P DATE SYMBOL PRICE +@end example + +@item N SYMBOL +Indicates that pricing information is to be ignored for a given +symbol, nor will quotes ever be downloaded for that symbol. Useful +with a home currency, such as the dollar ($). It is recommended that +these pricing options be set in the price database file, which +defaults to @file{~/.pricedb}. The syntax for this command is: +@example +N SYMBOL +@end example + +@item D AMOUNT +Specifies the default commodity to use, by specifying an amount in the +expected format. The @command{entry} command will use this commodity +as the default when none other can be determined. This command may be +used multiple times, to set the default flags for different +commodities; whichever is seen last is used as the default commodity. +For example, to set US dollars as the default commodity, while also +setting the thousands flag and decimal flag for that commodity, use: +@example +D $1,000.00 +@end example + +@item C AMOUNT1 = AMOUNT2 +Specifies a commodity conversion, where the first amount is given to +be equivalent to the second amount. The first amount should use the +decimal precision desired during reporting: +@example +C 1.00 Kb = 1024 bytes +@end example + +@item i, o, b, h +These four relate to timeclock support, which permits ledger to read +timelog files. See the timeclock's documentation for more info on the +syntax of its timelog files. +@end table + +@node Some typical queries, Budgeting and forecasting, File format, Running Ledger +@section Some typical queries + +A query such as the following shows all expenses since last +October, sorted by total: + +@example +ledger -b "last oct" -s -S T bal ^expenses +@end example + +From left to right the options mean: Show entries since October, 2003; +show all sub-accounts; sort by the absolute value of the total; and +report the balance for all expenses. + +@subsection Reporting monthly expenses + +The following query makes it easy to see monthly expenses, with each +month's expenses sorted by the amount: + +@example +ledger -M --period-sort t reg ^expenses +@end example + +Now, you might wonder where the money came from to pay for these +things. To see that report, add @option{-r}, which shows the +``related account'' transactions: + +@example +ledger -M --period-sort t -r reg ^expenses +@end example + +But maybe this prints too much information. You might just want to +see how much you're spending with your MasterCard. That kind of query +requires the use of a display predicate, since the transactions +calculated must match @samp{^expenses}, while the transactions +displayed must match @samp{mastercard}. The command would be: + +@example +ledger -M -r -d /mastercard/ reg ^expenses +@end example + +This query says: Report monthly subtotals; report the ``related +account'' transactions; display only related transactions whose +account matches @samp{mastercard}, and base the calculation on +transactions matching @samp{^expenses}. + +This works just as well for report the overall total, too: + +@example +ledger -s -r -d /mastercard/ reg ^expenses +@end example + +The @option{-s} option subtotals all transactions, just as @option{-M} +subtotaled by the month. The running total in both cases is off, +however, since a display expression is being used. + +@subsection Visualizing with Gnuplot + +If you have @command{Gnuplot} installed, you can graph any of the +above register reports. The script to do this is included in the +ledger distribution, and is named @file{scripts/report}. Install +@file{report} anywhere along your @env{PATH}, and then use +@command{report} instead of @command{ledger} when doing a register +report. The only thing to keep in mind is that you must specify +@option{-j} or @option{-J} to indicate whether Gnuplot should plot the +amount, or the running total. For example, this command plots total +monthly expenses made on your MasterCard. + +@example +report -j -M -r -d /mastercard/ reg ^expenses +@end example + +The @command{report} script is a very simple Bourne shell script, that +passes a set of scripted commands to Gnuplot. Feel free to modify the +script to your liking, since you may prefer histograms to line plots, +for example. + +@subsubsection Typical plots + +Here are some useful plots: + +@smallexample +report -j -M reg ^expenses # monthly expenses +report -J reg checking # checking account balance +report -J reg ^income ^expenses # cash flow report + +# net worth report, ignoring non-$ transactions + +report -J -l "Ua>=@{\$0.01@}" reg ^assets ^liab + +# net worth report starting last February. the use of a display +# predicate (-d) is needed, otherwise the balance will start at +# zero, and thus the y-axis will not reflect the true balance + +report -J -l "Ua>=@{\$0.01@}" -d "d>=[last feb]" reg ^assets ^liab +@end smallexample + +The last report uses both a calculation predicate (@option{-l}) and a +display predicate (@option{-d}). The calculation predicates limits +the report to transactions whose amount is greater than $1 (which can +only happen if the transaction amount is in dollars). The display +predicate limits the entries @emph{displayed} to just those since last +February, even those entries from before then will be computed as part +of the balance. + +@node Budgeting and forecasting, , Some typical queries, Running Ledger +@section Budgeting and forecasting + +@subsection Budgeting + +Keeping a budget allows you to pay closer attention to your income and +expenses, by reporting how far your actual financial activity is from +your expectations. + +To start keeping a budget, put some period entries at the top of your +ledger file. A period entry is almost identical to a regular entry, +except that it begins with a tilde and has a period expression in +place of a payee. For example: + +@smallexample +~ Monthly + Expenses:Rent $500.00 + Expenses:Food $450.00 + Expenses:Auto:Gas $120.00 + Expenses:Insurance $150.00 + Expenses:Phone $125.00 + Expenses:Utilities $100.00 + Expenses:Movies $50.00 + Expenses $200.00 ; all other expenses + Assets + +~ Yearly + Expenses:Auto:Repair $500.00 + Assets +@end smallexample + +These two period entries give the usual monthly expenses, as well as +one typical yearly expense. For help on finding out what your average +monthly expense is for any category, use a command like: + +@example +ledger -p "this year" -MAs bal ^expenses +@end example + +The reported totals are the current year's average for each account. + +Once these period entries are defined, creating a budget report is as +easy as adding @option{--budget} to the command-line. For example, a +typical monthly expense report would be: + +@example +ledger -M reg ^exp +@end example + +To see the same report balanced against your budget, use: + +@example +ledger --budget -M reg ^exp +@end example + +A budget report includes only those accounts that appear in the +budget. To see all expenses balanced against the budget, use +@option{--add-budget}. You can even see only the unbudgeted expenses +using @option{--unbudgeted}: + +@example +ledger --unbudgeted -M reg ^exp +@end example + +You can also use these flags with the @command{balance} command. + +@subsection Forecasting + +Sometimes it's useful to know what your finances will look like in the +future, such as determining when an account will reach zero. Ledger +makes this easy to do, using the same period entries as are used for +budgeting. An example forecast report can be generated with: + +@example +ledger --forecast "T>@{\$-500.00@}" register ^assets ^liabilities +@end example + +This report continues outputting transactions until the running total +is greater than $-500.00. A final transaction is always output, to +show you what the total afterwards would be. + +Forecasting can also be used with the balance report, but by date +only, and not against the running total: + +@example +ledger --forecast "d<[2010]" bal ^assets ^liabilities +@end example + +@node Keeping a ledger, Using XML, Running Ledger, Top +@chapter Keeping a ledger + +The most important part of accounting is keeping a good ledger. If +you have a good ledger, tools can be written to work whatever +mathematically tricks you need to better understand your spending +patterns. Without a good ledger, no tool, however smart, can help +you. + +The Ledger program aims at making ledger entry as simple as possible. +Since it is a command-line tool, it does not provide a user interface +for keeping a ledger. If you like, you may use GnuCash to maintain +your ledger, in which case the Ledger program will read GnuCash's data +files directly. In that case, read the GnuCash manual now, and skip +to the next chapter. + +If you are not using GnuCash, but a text editor to maintain your +ledger, read on. Ledger has been designed to make data entry as +simple as possible, by keeping the ledger format easy, and also by +automagically determining as much information as possible based on the +nature of your entries. + +For example, you do not need to tell Ledger about the accounts you +use. Any time Ledger sees a transaction involving an account it knows +nothing about, it will create it. If you use a commodity that is new +to Ledger, it will create that commodity, and determine its display +characteristics (placement of the symbol before or after the amount, +display precision, etc) based on how you used the commodity in the +transaction. + +Here is the Pacific Bell example from above, given as a Ledger +transaction: + +@smallexample +9/29 (100) Pacific Bell + Expenses:Utilities:Phone $23.00 + Assets:Checking $-23.00 +@end smallexample + +As you can see, it is very similar to what would be written on paper, +minus the computed balance totals, and adding in account names that +work better with Ledger's scheme of things. In fact, since Ledger is +smart about many things, you don't need to specify the balanced +amount, if it is the same as the first line: + +@smallexample +9/29 (100) Pacific Bell + Expenses:Utilities:Phone $23.00 + Assets:Checking +@end smallexample + +For this entry, Ledger will figure out that $-23.00 must come from +@samp{Assets:Checking} in order to balance the entry. + +@menu +* Stating where money goes:: +* Assets and Liabilities:: +* Commodities and Currencies:: +* Accounts and Inventories:: +* Understanding Equity:: +* Dealing with Petty Cash:: +* Working with multiple funds and accounts:: +* Archiving previous years:: +* Virtual transactions:: +* Automated transactions:: +* Using Emacs to Keep Your Ledger:: +* Using GnuCash to Keep Your Ledger:: +* Using timeclock to record billable time:: +@end menu + +@node Stating where money goes, Assets and Liabilities, Keeping a ledger, Keeping a ledger +@section Stating where money goes + +Accountants will talk of ``credits'' and ``debits'', but the meaning +is often different from the layman's understanding. To avoid +confusion, Ledger uses only subtractions and additions, although the +underlying intent is the same as standard accounting principles. + +Recall that every transaction will involve two or more accounts. +Money is transferred from one or more accounts to one or more other +accounts. To record the transaction, an amount is @emph{subtracted} +from the source accounts, and @emph{added} to the target accounts. + +In order to write a Ledger entry correctly, you must determine where +the money comes from and where it goes to. For example, when you are +paid a salary, you must add money to your bank account and also +subtract it from an income account: + +@smallexample +9/29 My Employer + Assets:Checking $500.00 + Income:Salary $-500.00 +@end smallexample + +Why is the Income a negative figure? When you look at the balance +totals for your ledger, you may be surprised to see that Expenses are +a positive figure, and Income is a negative figure. It may take some +getting used to, but to properly use a general ledger you must think +in terms of how money moves. Rather than Ledger ``fixing'' the minus +signs, let's understand why they are there. + +When you earn money, the money has to come from somewhere. Let's call +that somewhere ``society''. In order for society to give you an +income, you must take money away (withdraw) from society in order to +put it into (make a payment to) your bank. When you then spend that +money, it leaves your bank account (a withdrawal) and goes back to +society (a payment). This is why Income will appear negative---it +reflects the money you have drawn from society---and why Expenses will +be positive---it is the amount you've given back. These additions and +subtractions will always cancel each other out in the end, because you +don't have the ability to create new money: it must always come from +somewhere, and in the end must always leave. This is the beginning of +economy, after which the explanation gets terribly difficult. + +Based on that explanation, here's another way to look at your balance +report: every negative figure means that that account or person or +place has less money now than when you started your ledger; and every +positive figure means that that account or person or place has more +money now that when you started your ledger. Make sense? + +@node Assets and Liabilities, Commodities and Currencies, Stating where money goes, Keeping a ledger +@section Assets and Liabilities + +Assets are money that you have, and Liabilities are money that you +owe. ``Liabilities'' is just a more inclusive name for Debts. + +An Asset is typically increased by transferring money from an Income +account, such as when you get paid. Here is a typical entry: + +@smallexample +2004/09/29 My Employer + Assets:Checking $500.00 + Income:Salary +@end smallexample + +Money, here, comes from an Income account belonging to ``My +Employer'', and is transferred to your checking account. The money is +now yours, which makes it an Asset. + +Liabilities track money owed to others. This can happen when you +borrow money to buy something, or if you owe someone money. Here is +an example of increasing a MasterCard liability by spending money with +it: + +@smallexample +2004/09/30 Restaurant + Expenses:Dining $25.00 + Liabilities:MasterCard +@end smallexample + +The Dining account balance now shows $25 spent on Dining, and a +corresponding $25 owed on the MasterCard---and therefore shown as +$-25.00. The MasterCard liability shows up as negative because it +offsets the value of your assets. + +The combined total of your Assets and Liabilities is your net worth. +So to see your current net worth, use this command: + +@example +ledger balance ^assets ^liabilities +@end example + +Relatedly, your Income accounts show up negative, because they +transfer money @emph{from} an account in order to increase your +assets. Your Expenses show up positive because that is where the +money went to. The combined total of Income and Expenses is your cash +flow. A positive cash flow means you are spending more than you make, +since income is always a negative figure. To see your current cash +flow, use this command: + +@example +ledger balance ^income ^expenses +@end example + +Another common question to ask of your expenses is: How much do I +spend each month on X? Ledger provides a simple way of displaying +monthly totals for any account. Here is an example that summarizes +your monthly automobile expenses: + +@example +ledger -M register expenses:auto +@end example + +This assumes, of course, that you use account names like +@samp{Expenses:Auto:Gas} and @samp{Expenses:Auto:Repair}. + +@subsection Tracking reimbursable expenses + +Sometimes you will want to spend money on behalf of someone else, +which will eventually get repaid. Since the money is still ``yours'', +it is really an asset. And since the expenditure was for someone +else, you don't want it contaminating your Expenses reports. You will +need to keep an account for tracking reimbursements. + +This is fairly easy to do in ledger. When spending the money, spend +it @emph{to} your Assets:Reimbursements, using a different account for +each person or business that you spend money for. For example: + +@smallexample +2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard +@end smallexample + +This shows $100.00 spent on a MasterCard at Circuit City, with the +expense was made on behalf of Company XYZ. Later, when Company XYZ +pays the amount back, the money will transfer from that reimbursement +account back to a regular asset account: + +@smallexample +2004/09/29 Company XYZ + Assets:Checking $100.00 + Assets:Reimbursements:Company XYZ +@end smallexample + +This deposits the money owed from Company XYZ into a checking account, +presumably because they paid the amount back with a check. + +But what to do if you run your own business, and you want to keep +track of expenses made on your own behalf, while still tracking +everything in a single ledger file? This is more complex, because you +need to track two separate things: 1) The fact that the money should +be reimbursed to you, and 2) What the expense account was, so that you +can later determine where your company is spending its money. + +This kind of transaction is best handled with mirrored transactions in +two different files, one for your personal accounts, and one for your +company accounts. But keeping them in one file involves the same +kinds of transactions, so those are what is shown here. First, the +personal entry, which shows the need for reimbursement: + +@smallexample +2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard +@end smallexample + +This is the same as above, except that you own Company XYZ, and are +keeping track of its expenses in the same ledger file. This entry +should be immediately followed by an equivalent entry, which shows the +kind of expense, and also notes the fact that $100.00 is now payable +to you: + +@smallexample +2004/09/29 Circuit City + Company XYZ:Expenses:Computer:Software $100.00 + Company XYZ:Accounts Payable:Your Name +@end smallexample + +This second entry shows that Company XYZ has just spent $100.00 on +software, and that this $100.00 came from Your Name, which must be +paid back. + +These two entries can also be merged, to make things a little clearer. +Note that all amounts must be specified now: + +@smallexample +2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard $-100.00 + Company XYZ:Expenses:Computer:Software $100.00 + Company XYZ:Accounts Payable:Your Name $-100.00 +@end smallexample + +To ``pay back'' the reimbursement, just reverse the order of +everything, except this time drawing the money from a company asset, +paying it to accounts payable, and then drawing it again from the +reimbursement account, and paying it to your personal asset account. +It's easier shown than said: + +@smallexample +2004/10/15 Company XYZ + Assets:Checking $100.00 + Assets:Reimbursements:Company XYZ $-100.00 + Company XYZ:Accounts Payable:Your Name $100.00 + Company XYZ:Assets:Checking $-100.00 +@end smallexample + +And now the reimbursements account is paid off, accounts payable is +paid off, and $100.00 has been effectively transferred from the +company's checking account to your personal checking account. The +money simply ``waited''---in both @samp{Assets:Reimbursements:Company +XYZ}, and @samp{Company XYZ:Accounts Payable:Your Name}---until such +time as it could be paid off. + +The value of tracking expenses from both sides like that is that you +do not contaminate your personal expense report with expenses made on +behalf of others, while at the same time making it possible to +generate accurate reports of your company's expenditures. It is more +verbose than just paying for things with your personal assets, but it +gives you a very accurate information trail. + +The advantage to keep these doubled entries together is that they +always stay in sync. The advantage to keeping them apart is that it +clarifies the transfer's point of view. To keep the transactions in +separate files, just separate the two entries that were joined above. +For example, for both the expense and the pay-back shown above, the +following four entries would be created. Two in your personal ledger +file: + +@smallexample +2004/09/29 Circuit City + Assets:Reimbursements:Company XYZ $100.00 + Liabilities:MasterCard $-100.00 + +2004/10/15 Company XYZ + Assets:Checking $100.00 + Assets:Reimbursements:Company XYZ $-100.00 +@end smallexample + +And two in your company ledger file: + +@smallexample +!account Company XYZ + +2004/09/29 Circuit City + Expenses:Computer:Software $100.00 + Accounts Payable:Your Name $-100.00 + +2004/10/15 Company XYZ + Accounts Payable:Your Name $100.00 + Assets:Checking $-100.00 + +!end +@end smallexample + +(Note: The @samp{!account} above means that all accounts mentioned in +the file are children of that account. In this case it means that all +activity in the file relates to Company XYZ). + +After creating these entries, you will always know that $100.00 was +spent using your MasterCard on behalf of Company XYZ, and that Company +XYZ spent the money on computer software and paid it back about two +weeks later. + +@node Commodities and Currencies, Accounts and Inventories, Assets and Liabilities, Keeping a ledger +@section Commodities and Currencies + +Ledger makes no assumptions about the commodities you use; it only +requires that you specify a commodity. The commodity may be any +non-numeric string that does not contain a period, comma, forward +slash or at-sign. It may appear before or after the amount, although +it is assumed that symbols appearing before the amount refer to +currencies, while non-joined symbols appearing after the amount refer +to commodities. Here are some valid currency and commodity +specifiers: + +@example +$20.00 ; currency: twenty US dollars +40 AAPL ; commodity: 40 shares of Apple stock +60 DM ; currency: 60 Deutsch Mark +£50 ; currency: 50 British pounds +50 EUR ; currency: 50 Euros (or use appropriate symbol) +@end example + +Ledger will examine the first use of any commodity to determine how +that commodity should be printed on reports. It pays attention to +whether the name of commodity was separated from the amount, whether +it came before or after, the precision used in specifying the amount, +whether thousand marks were used, etc. This is done so that printing +the commodity looks the same as the way you use it. + +An account may contain multiple commodities, in which case it will +have separate totals for each. For example, if your brokerage account +contains both cash, gold, and several stock quantities, the balance +might look like: + +@smallexample + $200.00 +100.00 AU + AAPL 40 + BORL 100 + FEQTX 50 Assets:Brokerage +@end smallexample + +This balance report shows how much of each commodity is in your +brokerage account. + +Sometimes, you will want to know the current street value of your +balance, and not the commodity totals. For this to happen, you must +specify what the current price is for each commodity. The price can +be any commodity, in which case the balance will be computed in terms +of that commodity. The usual way to specify prices is with a price +history file, which might look like this: + +@smallexample +P 2004/06/21 02:18:01 FEQTX $22.49 +P 2004/06/21 02:18:01 BORL $6.20 +P 2004/06/21 02:18:02 AAPL $32.91 +P 2004/06/21 02:18:02 AU $400.00 +@end smallexample + +Specify the price history to use with the @option{--price-db} option, +with the @option{-V} option to report in terms of current market +value: + +@example +ledger --price-db prices.db -V balance brokerage +@end example + +The balance for your brokerage account will be reported in US dollars, +since the prices database uses that currency. + +@smallexample +$40880.00 Assets:Brokerage +@end smallexample + +You can convert from any commodity to any other commodity. Let's say +you had $5000 in your checking account, and for whatever reason you +wanted to know many ounces of gold that would buy, in terms of the +current price of gold: + +@example +ledger -T "@{1 AU@}*(O/P@{1 AU@})" balance checking +@end example + +Although the total expression appears complex, it is simply saying +that the reported total should be in multiples of AU units, where the +quantity is the account total divided by the price of one AU. Without +the initial multiplication, the reported total would still use the +dollars commodity, since multiplying or dividing amounts always keeps +the left value's commodity. The result of this command might be: + +@smallexample +14.01 AU Assets:Checking +@end smallexample + +@subsection Commodity price histories + +Whenever a commodity is purchased using a different commodity (such as +a share of common stock using dollars), it establishes a price for +that commodity on that day. It is also possible, by recording price +details in a ledger file, to specify other prices for commodities at +any given time. Such price entries might look like those below: + +@smallexample +P 2004/06/21 02:17:58 TWCUX $27.76 +P 2004/06/21 02:17:59 AGTHX $25.41 +P 2004/06/21 02:18:00 OPTFX $39.31 +P 2004/06/21 02:18:01 FEQTX $22.49 +P 2004/06/21 02:18:02 AAPL $32.91 +@end smallexample + +By default, ledger will not consider commodity prices when generating +its various reports. It will always report balances in terms of the +commodity total, rather than the current value of those commodities. +To enable pricing reports, use one of the commodity reporting options. + +@subsection Commodity equivalencies + +Sometimes a commodity has several forms which are all equivalent. An +example of this is time. Whether tracked in terms of minutes, hours +or days, it should be possible to convert between the various forms. +Doing this requires the use of commodity equivalencies. + +For example, you might have the following two transactions, one which +transfers an hour of time into a @samp{Billable} account, and another +which decreases the same account by ten minutes. The resulting report +will indicate that fifty minutes remain: + +@smallexample +2005/10/01 Work done for company + Billable:Client 1h + Project:XYZ + +2005/10/02 Return ten minutes to the project + Project:XYZ 10m + Billable:Client +@end smallexample + +Reporting the balance for this ledger file produces: + +@smallexample + 50.0m Billable:Client + -50.0m Project:XYZ +@end smallexample + +This example works because ledger already knows how to handle seconds, +minutes and hours, as part of its time tracking support. Defining +other equivalencies is simple. The following is an example that +creates data equivalencies, helpful for tracking bytes, kilobytes, +megabytes, and more: + +@smallexample +C 1.00 Kb = 1024 b +C 1.00 Mb = 1024 Kb +C 1.00 Gb = 1024 Mb +C 1.00 Tb = 1024 Gb +@end smallexample + +Each of these definitions correlates a commodity (such as @samp{Kb}) +and a default precision, with a certain quantity of another commodity. +In the above example, kilobytes are reporetd with two decimal places +of precision and each kilobyte is equal to 1024 bytes. + +Equivalency chains can be as long as desired. Whenever a commodity +would report as a decimal amount (less than @samp{1.00}), the next +smallest commodity is used. If a commodity could be reported in terms +of a higher commodity without resulting to a partial fraction, then +the larger commodity is used. + +@node Accounts and Inventories, Understanding Equity, Commodities and Currencies, Keeping a ledger +@section Accounts and Inventories + +Since Ledger's accounts and commodity system is so flexible, you can +have accounts that don't really exist, and use commodities that no one +else recognizes. For example, let's say you are buying and selling +various items in EverQuest, and want to keep track of them using a +ledger. Just add items of whatever quantity you wish into your +EverQuest account: + +@smallexample +9/29 Get some stuff at the Inn + Places:Black's Tavern -3 Apples + Places:Black's Tavern -5 Steaks + EverQuest:Inventory +@end smallexample + +Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The +amounts are negative, because you are taking @emph{from} Black's +Tavern in order to add to your Inventory account. Note that you don't +have to use @samp{Places:Black's Tavern} as the source account. You +could use @samp{EverQuest:System} to represent the fact that you +acquired them online. The only purpose for choosing one kind of +source account over another is for generate more informative reports +later on. The more you know, the better analysis you can perform. + +If you later sell some of these items to another player, the entry +would look like: + +@smallexample +10/2 Sturm Brightblade + EverQuest:Inventory -2 Steaks + EverQuest:Inventory 15 Gold +@end smallexample + +Now you've turned 2 steaks into 15 gold, courtesy of your customer, +Sturm Brightblade. + +@node Understanding Equity, Dealing with Petty Cash, Accounts and Inventories, Keeping a ledger +@section Understanding Equity + +The most confusing entry in any ledger will be your equity account--- +because starting balances can't come out of nowhere. + +When you first start your ledger, you will likely already have money +in some of your accounts. Let's say there's $100 in your checking +account; then add an entry to your ledger to reflect this amount. +Where will money come from? The answer: your equity. + +@smallexample +10/2 Opening Balance + Assets:Checking $100.00 + Equity:Opening Balances +@end smallexample + +But what is equity? You may have heard of equity when people talked +about house mortgages, as ``the part of the house that you own''. +Basically, equity is like the value of something. If you own a car +worth $5000, then you have $5000 in equity in that car. In order to +turn that car (a commodity) into a cash flow, or a credit to your bank +account, you will have to debit the equity by selling it. + +When you start a ledger, you are probably already worth something. +Your net worth is your current equity. By transferring the money in +the ledger from your equity to your bank accounts, you are crediting +the ledger account based on your prior equity. That is why, when you +look at the balance report, you will see a large negative number for +Equity that never changes: Because that is what you were worth (what +you debited from yourself in order to start the ledger) before the +money started moving around. If the total positive value of your +assets is greater than the absolute value of your starting equity, it +means you are making money. + +Clear as mud? Keep thinking about it. Until you figure it out, put +@samp{-Equity} at the end of your balance command, to remove the +confusing figure from the total. + +@node Dealing with Petty Cash, Working with multiple funds and accounts, Understanding Equity, Keeping a ledger +@section Dealing with Petty Cash + +Something that stops many people from keeping a ledger at all is the +insanity of tracking small cash expenses. They rarely generate a +receipt, and there are often a lot of small transactions, rather than +a few large ones, as with checks. + +One solution is: don't bother. Move your spending to a debit card, +but in general ignore cash. Once you withdraw it from the ATM, mark +it as already spent to an @samp{Expenses:Cash} category: + +@smallexample +2004/03/15 ATM + Expenses:Cash $100.00 + Assets:Checking +@end smallexample + +If at some point you make a large cash expense that you want to track, +just ``move'' the amount of the expense from @samp{Expenses:Cash} into +the target account: + +@smallexample +2004/03/20 Somebody + Expenses:Food $65.00 + Expenses:Cash +@end smallexample + +This way, you can still track large cash expenses, while ignoring all +of the smaller ones. + +@node Working with multiple funds and accounts, Archiving previous years, Dealing with Petty Cash, Keeping a ledger +@section Working with multiple funds and accounts + +There are situations when the accounts you're tracking are different +between your clients and the financial institutions where money is +kept. An example of this is working as the treasurer for a religious +institution. From the secular point of view, you might be working +with three different accounts: + +@itemize +@item Checking +@item Savings +@item Credit Card +@end itemize + +From a religious point of view, the community expects to divide its +resources into multiple ``funds'', from which it makes purchases or +reserves resources for later: + +@itemize +@item School fund +@item Building fund +@item Community fund +@end itemize + +The problem with this kind of setup is that when you spend money, it +comes from two or more places at once: the account and the fund. And +yet, the correlation of amounts between funds and accounts is rarely +one-to-one. What if the school fund has @samp{$500.00}, but +@samp{$400.00} of that comes from Checking, and @samp{$100.00} from +Savings? + +Traditional finance packages require that the money reside in only one +place. But there are really two ``views'' of the data: from the +account point of view and from the fund point of view -- yet both sets +should reflect the same overall expenses and cash flow. It's simply +where the money resides that differs. + +This situation can be handled one of two ways. The first is using +virtual transactions to represent the fact that money is moving to and +from two kind of accounts at the same time: + +@smallexample +2004/03/20 Contributions + Assets:Checking $500.00 + Income:Donations + +2004/03/25 Distribution of donations + [Funds:School] $300.00 + [Funds:Building] $200.00 + [Assets:Checking] $-500.00 +@end smallexample + +The use of square brackets in the second entry ensures that the +virtual transactions balance to zero. Now money can be spent directly +from a fund at the same time as money is drawn from a physical +account: + +@smallexample +2004/03/25 Payment for books (paid from Checking) + Expenses:Books $100.00 + Assets:Checking $-100.00 + (Funds:School) $-100.00 +@end smallexample + +When reports are generated, by default they'll appear in terms of the +funds. In this case, you will likely want to mask out your +@samp{Assets} account, because otherwise the balance won't make much +sense: + +@example +ledger bal -^Assets +@end example + +If the @option{--real} option is used, the report will be in terms of +the real accounts: + +@example +ledger --real bal +@end example + +If more asset accounts are needed as the source of a transaction, just +list them as you would normally, for example: + +@smallexample +2004/03/25 Payment for books (paid from Checking) + Expenses:Books $100.00 + Assets:Checking $-50.00 + Liabilities:Credit Card $-50.00 + (Funds:School) $-100.00 +@end smallexample + +The second way of tracking funds is to use entry codes. In this +respect the codes become like virtual accounts that embrace the entire +set of transactions. Basically, we are associating an entry with a +fund by setting its code. Here are two entries that desposit money +into, and spend money from, the @samp{Funds:School} fund: + +@smallexample +2004/03/25 (Funds:School) Donations + Assets:Checking $100.00 + Income:Donations + +2004/04/25 (Funds:School) Payment for books + Expenses:Books $50.00 + Assets:Checking +@end smallexample + +Note how the accounts now relate only to the real accounts, and any +balance or registers reports will reflect this. That the entries +relate to a particular fund is kept only in the code. + +How does this become a fund report? By using the +@option{--code-as-payee} option, you can generate a register report +where the payee for each transaction shows the code. Alone, this is +not terribly interesting; but when combined with the +@option{--by-payee} option, you will now see account subtotals for any +transactions related to a specific fund. So, to see the current +monetary balances of all funds, the command would be: + +@smallexample +ledger --code-as-payee -P reg ^Assets +@end smallexample + +Or to see a particular funds expenses, the @samp{School} fund in this +case: + +@smallexample +ledger --code-as-payee -P reg ^Expenses -- School +@end smallexample + +Both approaches yield different kinds of flexibility, depending on how +you prefer to think of your funds: as virtual accounts, or as tags +associated with particular entries. Your own tastes will decide which +is best for your situation. + +@node Archiving previous years, Virtual transactions, Working with multiple funds and accounts, Keeping a ledger +@section Archiving previous years + +After a while, your ledger can get to be pretty large. While this +will not slow down the ledger program much---it's designed to process +ledger files very quickly---things can start to feel ``messy''; and +it's a universal complaint that when finances feel messy, people avoid +them. + +Thus, archiving the data from previous years into their own files can +offer a sense of completion, and freedom from the past. But how to +best accomplish this with the ledger program? There are two commands +that make it very simple: @command{print}, and @command{equity}. + +Let's take an example file, with data ranging from year 2000 until +2004. We want to archive years 2000 and 2001 to their own file, +leaving just 2003 and 2004 in the current file. So, use +@command{print} to output all the earlier entries to a file called +@file{ledger-old.dat}: + +@smallexample +ledger -f ledger.dat -b 2000 -e 2001 print > ledger-old.dat +@end smallexample + +To delete older data from the current ledger file, use @command{print} +again, this time specifying year 2002 as the starting date: + +@example +ledger -f ledger.dat -b 2002 print > x +mv x ledger.dat +@end example + +However, now the current file contains @emph{only} transactions from +2002 onward, which will not yield accurate present-day balances, +because the net income from previous years is no longer being tallied. +To compensate for this, we must append an equity report for the old +ledger at the beginning of the new one: + +@example +ledger -f ledger-old.dat equity > equity.dat +cat equity.dat ledger.dat > x +mv x ledger.dat +rm equity.dat +@end example + +Now the balances reported from @file{ledger.dat} are identical to what +they were before the data was split. + +How often should you split your ledger? You never need to, if you +don't want to. Even eighty years of data will not slow down ledger +much---and that's just using present day hardware! Or, you can keep +the previous and current year in one file, and each year before that +in its own file. It's really up to you, and how you want to organize +your finances. For those who also keep an accurate paper trail, it +might be useful to archive the older years to their own files, then +burn those files to a CD to keep with the paper records---along with +any electronic statements received during the year. In the arena of +organization, just keep in mind this maxim: Do whatever keeps you +doing it. + +@node Virtual transactions, Automated transactions, Archiving previous years, Keeping a ledger +@section Virtual transactions + +A virtual transaction is when you, in your mind, see money as moving +to a certain place, when in reality that money has not moved at all. +There are several scenarios in which this type of tracking comes in +handy, and each of them will be discussed in detail. + +To enter a virtual transaction, surround the account name in +parentheses. This form of usage does not need to balance. However, +if you want to ensure the virtual transaction balances with other +virtual transactions in the same entry, use square brackets. For +example: + +@smallexample +10/2 Paycheck + Assets:Checking $1000.00 + Income:Salary $-1000.00 + (Debt:Alimony) $200.00 +@end smallexample + +In this example, after receiving a paycheck an alimony debt is +increased---even though no money has moved around yet. + +@smallexample +10/2 Paycheck + Assets:Checking $1000.00 + Income:Salary $-1000.00 + [Savings:Trip] $200.00 + [Assets:Checking] $-200.00 +@end smallexample + +In this example, $200 has been deducted from checking toward savings +for a trip. It will appear as though the money has been moved from +the account into @samp{Savings:Trip}, although no money has actually +moved anywhere. + +When balances are displayed, virtual transactions will be factored in. +To view balances without any virtual balances factored in, using the +@option{-R} flag, for ``reality''. + +@node Automated transactions, Using Emacs to Keep Your Ledger, Virtual transactions, Keeping a ledger +@section Automated transactions + +As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire assets. +It is similar to tithing for Jews and Christians, or to Zakát for +Muslims. The exact details of computing Huqúqu'lláh are somewhat +complex, but if you have further interest, please consult the Web. + +Ledger makes this otherwise difficult law very easy. Just set up an +automated transaction at the top of your ledger file: + +@smallexample +; This automated entry will compute Huqúqu'lláh based on this +; journal's transactions. Any that match will affect the +; Liabilities:Huququ'llah account by 19% of the value of that +; transaction. + += /^(?:Income:|Expenses:(?:Business|Rent$|Furnishings|Taxes|Insurance))/ + (Liabilities:Huququ'llah) 0.19 +@end smallexample + +This automated transaction works by looking at each transaction in the +ledger file. If any match the given value expression, 19% of the +transaction's value is applied to the @samp{Liabilities:Huququ'llah} +account. So, if $1000 is earned from @samp{Income:Salary}, $190 is +added to @samp{Liabilities:Huqúqu'lláh}; if $1000 is spent on Rent, +$190 is subtracted. The ultimate balance of Huqúqu'lláh reflects how +much is owed in order to fulfill one's obligation to Huqúqu'lláh. +When ready to pay, just write a check to cover the amount shown in +@samp{Liabilities:Huququ'llah}. That entry would look like: + +@smallexample +2003/01/01 (101) Baha'i Huqúqu'lláh Trust + Liabilities:Huququ'llah $1,000.00 + Assets:Checking +@end smallexample + +That's it. To see how much Huqúq is currently owed based on your +ledger entries, use: + +@example +ledger balance Liabilities:Huquq +@end example + +This works fine, but omits one aspect of the law: that Huquq is only +due once the liability exceeds the value of 19 mithqáls of gold (which +is roughly 2.22 ounces). So what we want is for the liability to +appear in the balance report only when it exceeds the present day +value of 2.22 ounces of gold. This can be accomplished using the +command: + +@smallexample +ledger -Q -t "/Liab.*Huquq/?(a/P@{2.22 AU@}<=@{-1.0@}&a):a" -s bal liab +@end smallexample + +With this command, the current price for gold is downloaded, and the +Huqúqu'lláh is reported only if its value exceeds that of 2.22 ounces +of gold. If you wish the liability to be reflected in the parent +subtotal either way, use this instead: + +@smallexample +ledger -Q -T "/Liab.*Huquq/?(O/P@{2.22 AU@}<=@{-1.0@}&O):O" -s bal liab +@end smallexample + +In some cases, you may wish to refer to the account of whichever +transaction matched your automated entry's value expression. To do +this, use the special account name @samp{$account}: + +@smallexample += /^Some:Long:Account:Name/ + [$account] -0.10 + [Savings] 0.10 +@end smallexample + +This example causes 10% of the matching account's total to be deferred +to the @samp{Savings} account---as a balanced virtual transaction, +which may be excluded from reports by using @option{--real}. + +@node Using Emacs to Keep Your Ledger, Using GnuCash to Keep Your Ledger, Automated transactions, Keeping a ledger +@section Using Emacs to Keep Your Ledger + +In the Ledger tarball is an Emacs module, @file{ledger.el}. This +module makes the process of keeping a text ledger much easier for +Emacs users. I recommend putting this at the top of your ledger file: + +@example +; -*-ledger-*- +@end example + +And this in your @file{.emacs} file, after copying @file{ledger.el} to +your @file{site-lisp} directory: + +@example +(load "ledger") +@end example + +Now when you edit your ledger file, it will be in +@command{ledger-mode}. @command{ledger-mode} adds these commands: + +@table @strong +@item C-c C-a +For quickly adding new entries based on the form of older ones (see +previous section). + +@item C-c C-c +Toggles the ``cleared'' flag of the transaction under point. + +@item C-c C-d +Delete the entry under point. + +@item C-c C-r +Reconciles an account by displaying the transactions in another +buffer, where simply hitting the spacebar will toggle the pending flag +of the transaction in the ledger. Once all the appropriate +transactions have been marked, press C-c C-c in the reconcile buffer +to ``commit'' the reconciliation, which will mark all of the entries +as cleared, and display the new cleared balance in the minibuffer. + +@item C-c C-m +Set the default month for new entries added with C-c C-a. This is +handy if you have a large number of transactions to enter from a +previous month. + +@item C-c C-y +Set the default year for new entries added with C-c C-a. This is +handy if you have a large number of transactions to enter from a +previous year. +@end table + +Once you enter the reconcile buffer, there are several key commands +available: + +@table @strong +@item RET +Visit the ledger file entry corresponding to the reconcile entry. + +@item C-c C-c +Commit the reconcialation. This marks all of the marked transactions +as ``cleared'', saves the ledger file, and then displays the new +cleared balance. + +@item C-l +Refresh the reconcile buffer by re-reading transactions from the +ledger data file. + +@item SPC +Toggle the transaction under point as cleared. + +@item a +Add a new entry to the ledger data file, and refresh the reconcile +buffer to include its transactions (if the entry is added to the same +account as the one being reconciled). + +@item d +Delete the entry related to the transaction under point. Note: This +may result in multiple transactions being deleted. + +@item n +Move to the next line. + +@item p +Move to the previous line. + +@item C-c C-r +@item r +Attempt to auto-reconcile the transactions to the entered balance. If +it can do so, it will mark all those transactions as pending that +would yield the specified balance. + +@item C-x C-s +@item s +Save the ledger data file, and show the current cleared balance for +the account being reconciled. + +@item q +Quit the reconcile buffer. +@end table + +There is also an @command{emacs} command which can be used to output +reports in a format directly @code{read}-able from Emacs Lisp. + +@node Using GnuCash to Keep Your Ledger, Using timeclock to record billable time, Using Emacs to Keep Your Ledger, Keeping a ledger +@section Using GnuCash to Keep Your Ledger + +The Ledger tool is fast and simple, but it offers no custom method for +actually editing the ledger. It assumes you know how to use a text +editor, and like doing so. Perhaps an Emacs mode will appear someday +soon to make editing Ledger's data files much easier. + +Until then, you are free to use GnuCash to maintain your ledger, and +the Ledger program for querying and reporting on the contents of that +ledger. It takes a little longer to parse the XML data format that +GnuCash uses, but the end result is identical. + +Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth +to edit their data, and a one megabyte binary to query it? + +@node Using timeclock to record billable time, , Using GnuCash to Keep Your Ledger, Keeping a ledger +@section Using timeclock to record billable time + +The timeclock tool makes it easy to track time events, like clocking +into and out of a particular job. These events accumulate in a +timelog file. + +Each in/out event may have an optional description. If the ``in'' +description is a ledger account name, these in/out pairs may be viewed +as virtual transactions, adding time commodities (hours) to that +account. + +For example, the command-line version of the timeclock tool could be +used to begin a timelog file like: + +@example +export TIMELOG=$HOME/.timelog +ti ClientOne category +sleep 10 +to waited for ten seconds +@end example + +The @file{.timelog} file now contains: + +@smallexample +i 2004/10/06 15:21:00 ClientOne category +o 2004/10/06 15:21:10 waited for ten seconds +@end smallexample + +Ledger parses this directly, as if it had seen the following entry: + +@smallexample +2004/10/06 category + (ClientOne) 10s +@end smallexample + +In other words, the timelog event pair is seen as adding 0.00277h (ten +seconds) worth of time to the @samp{ClientOne} account. This would be +considered billable time, which later could be invoiced and credited +to accounts receivable: + +@smallexample +2004/11/01 (INV#1) ClientOne, Inc. + Receivable:ClientOne $0.10 + ClientOne -0.00277h @@ $35.00 +@end smallexample + +The above transaction converts the clocked time into an invoice for +the time spent, at an hourly rate of $35. Once the invoice is paid, +the money is deposited from the receivable account into a checking +account: + +@smallexample +2004/12/01 ClientOne, Inc. + Assets:Checking $0.10 + Receivable:ClientOne +@end smallexample + +And now the time spent has been turned into hard cash in the checking +account. + +The advantage to using timeclock and invoicing to bill time is that +you will always know, by looking at the balance report, exactly how +much unbilled and unpaid time you've spent working for any particular +client. + +I like to @samp{!include} my timelog at the top of my company's +accounting ledger, with the attached prefix @samp{Billable}: + +@smallexample +; -*-ledger-*- + +; This is the ledger file for my company. But first, include the +; timelog data, entering all of the time events within the umbrella +; account "Billable". + +!account Billable +!include /home/johnw/.timelog +!end + +; Here follows this fiscal year's transactions for the company. + +2004/11/01 (INV#1) ClientOne, Inc. + Receivable:ClientOne $0.10 + Billable:ClientOne -0.00277h @@ $35.00 + +2004/12/01 ClientOne, Inc. + Assets:Checking $0.10 + Receivable:ClientOne +@end smallexample + +@node Using XML, , Keeping a ledger, Top +@chapter Using XML + +By default, Ledger uses a human-readable data format, and displays its +reports in a manner meant to be read on screen. For the purpose of +writing tools which use Ledger, however, it is possible to read and +display data using XML. This chapter documents that format. + +The general format used for Ledger data is: + +@smallexample + + + ... + ... + ...... + +@end smallexample + +The data stream is enclosed in a @samp{ledger} tag, which contains a +series of one or more entries. Each @samp{entry} describes the entry +and contains a series of one or more transactions: + +@smallexample + + 2004/03/01 + + 100 + John Wiegley + + ... + ... + ...... + + +@end smallexample + +The date format for @samp{en:date} is always @samp{YYYY/MM/DD}. The +@samp{en:cleared} tag is optional, and indicates whether the +transaction has been cleared or not. There is also an +@samp{en:pending} tag, for marking pending transactions. The +@samp{en:code} and @samp{en:payee} tags both contain whatever text the +user wishes. + +After the initial entry data, there must follow a set of transactions +marked with @samp{en:transactions}. Typically these transactions will +all balance each other, but if not they will be automatically balanced +into an account named @samp{}. + +Within the @samp{en:transactions} tag is a series of one or more +@samp{transaction}'s, which have the following form: + +@smallexample + + Expenses:Computer:Hardware + + + + $ + 90.00 + + + + +@end smallexample + +This is a basic transaction. It may also be begin with +@samp{tr:virtual} and/or @samp{tr:generated} tags, to indicate virtual +and auto-generated transactions. Then follows the @samp{tr:account} +tag, which contains the full name of the account the transaction is +related to. Colons separate parent from child in an account name. + +Lastly follows the amount of the transaction, indicated by +@samp{tr:amount}. Within this tag is a @samp{value} tag, of which +there are four different kinds, each with its own format: + +@enumerate +@item boolean +@item integer +@item amount +@item balance +@end enumerate + +The format of a boolean value is @samp{true} or @samp{false} +surrounded by a @samp{boolean} tag, for example: + +@smallexample +true +@end smallexample + +The format of an integer value is the numerical value surrounded by an +@samp{integer} tag, for example: + +@smallexample +12036 +@end smallexample + +The format of an amount contains two members, the commodity and the +quantity. The commodity can have a set of flags that indicate how to +display it. The meaning of the flags (all of which are optional) are: + +@table @strong +@item P +The commodity is prefixed to the value. +@item S +The commodity is separated from the value by a space. +@item T +Thousands markers are used to display the amount. +@item E +The format of the amount is European, with period used as a thousands +marker, and comma used as the decimal point. +@end table + +The actual quantity for an amount is an integer of arbitrary size. +Ledger uses the GNU multi-precision math library to handle such +values. The XML format assumes the reader to be equally capable. +Here is an example amount: + +@smallexample + + + $ + 90.00 + + +@end smallexample + +Lastly, a balance value contains a series of amounts, each with a +different commodity. Unlike the name, such a value does need to +balance. It is called a balance because it sums several amounts. For +example: + +@smallexample + + + + $ + 90.00 + + + DM + 200.00 + + + +@end smallexample + +That is the extent of the XML data format used by Ledger. It will +output such data if the @command{xml} command is used, and can read +the same data as long as the @file{expat} library was available +when Ledger was built. + +@bye diff --git a/emacs.cc b/emacs.cc deleted file mode 100644 index e69de29b..00000000 diff --git a/emacs.h b/emacs.h deleted file mode 100644 index e69de29b..00000000 diff --git a/error.h b/error.h deleted file mode 100644 index 5cbf54fb..00000000 --- a/error.h +++ /dev/null @@ -1,151 +0,0 @@ -#ifndef _ERROR_H -#define _ERROR_H - -#import "context.h" - -namespace ledger { - -class exception : public std::exception -{ -protected: - string reason; - -public: - std::list context_stack; - - exception(const string& _reason, - const context& immediate_ctxt) throw() - : reason(_reason) { - EXCEPTION(reason); - push(immediate_ctxt); - } - - virtual ~exception() throw() {} - - void push(const context& intermediate_ctxt) throw() { - context_stack.push_front(intermediate_ctxt); - } - - void write(std::ostream& out) const throw() { -#if 0 - for (std::list::const_iterator - i = context_stack.begin(); - i != context_stack.end(); - i++) - (*i).write(out); -#endif - } - - const char * what() const throw() { - return reason.c_str(); - } -}; - -#define DECLARE_EXCEPTION(name) \ - class name : public exception { \ - public: \ - name(const string& _reason, \ - const context& immediate_ctxt) throw() \ - : exception(_reason, immediate_ctxt) {} \ - } - -#if 0 - -class error_context -{ - public: - string desc; - - error_context(const 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: - string file; - unsigned long line; - public: - file_context(const string& _file, unsigned long _line, - const 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: - string line; - long pos; - - line_context(const string& _line, long _pos, - const 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 error : public str_exception { - public: - error(const string& _reason, error_context * _ctxt = NULL) throw() - : str_exception(_reason, _ctxt) {} - virtual ~error() throw() {} -}; - -class fatal : public str_exception { - public: - fatal(const string& _reason, error_context * _ctxt = NULL) throw() - : str_exception(_reason, _ctxt) {} - virtual ~fatal() throw() {} -}; - -class fatal_assert : public fatal { - public: - fatal_assert(const string& _reason, error_context * _ctxt = NULL) throw() - : fatal(string("assertion failed '") + _reason + "'", _ctxt) {} - virtual ~fatal_assert() throw() {} -}; - -#endif // 0 - -inline void unexpected(char c, char wanted) -{ -#if 0 - if ((unsigned char) c == 0xff) { - if (wanted) - throw new error(string("Missing '") + wanted + "'"); - else - throw new error("Unexpected end of input"); - } else { - if (wanted) - throw new error(string("Invalid char '") + c + - "' (wanted '" + wanted + "')"); - else - throw new error(string("Invalid char '") + c + "'"); - } -#endif -} - -} // namespace ledger - -#endif // _ERROR_H diff --git a/fdstream.hpp b/fdstream.hpp deleted file mode 100644 index a74a5781..00000000 --- a/fdstream.hpp +++ /dev/null @@ -1,184 +0,0 @@ -/* The following code declares classes to read from and write to - * file descriptore or file handles. - * - * See - * http://www.josuttis.com/cppcode - * for details and the latest version. - * - * - open: - * - integrating BUFSIZ on some systems? - * - optimized reading of multiple characters - * - stream for reading AND writing - * - i18n - * - * (C) Copyright Nicolai M. Josuttis 2001. - * Permission to copy, use, modify, sell and distribute this software - * is granted provided this copyright notice appears in all copies. - * This software is provided "as is" without express or implied - * warranty, and with no claim as to its suitability for any purpose. - * - * Version: Jul 28, 2002 - * History: - * Jul 28, 2002: bugfix memcpy() => memmove() - * fdinbuf::underflow(): cast for return statements - * Aug 05, 2001: first public version - */ -#ifndef BOOST_FDSTREAM_HPP -#define BOOST_FDSTREAM_HPP - -#include -#include -#include -// for EOF: -#include -// for memmove(): -#include - - -// low-level read and write functions -#ifdef _MSC_VER -# include -#else -# include -//extern "C" { -// int write (int fd, const char* buf, int num); -// int read (int fd, char* buf, int num); -//} -#endif - - -// BEGIN namespace BOOST -namespace boost { - - -/************************************************************ - * fdostream - * - a stream that writes on a file descriptor - ************************************************************/ - - -class fdoutbuf : public std::streambuf { - protected: - int fd; // file descriptor - public: - // constructor - fdoutbuf (int _fd) : fd(_fd) { - } - protected: - // write one character - virtual int_type overflow (int_type c) { - if (c != EOF) { - char z = c; - if (write (fd, &z, 1) != 1) { - return EOF; - } - } - return c; - } - // write multiple characters - virtual - std::streamsize xsputn (const char* s, - std::streamsize num) { - return write(fd,s,num); - } -}; - -class fdostream : public std::ostream { - protected: - fdoutbuf buf; - public: - fdostream (int fd) : std::ostream(0), buf(fd) { - rdbuf(&buf); - } -}; - - -/************************************************************ - * fdistream - * - a stream that reads on a file descriptor - ************************************************************/ - -class fdinbuf : public std::streambuf { - protected: - int fd; // file descriptor - 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() - */ - fdinbuf (int _fd) : fd(_fd) { - setg (buffer+pbSize, // beginning of putback area - buffer+pbSize, // read position - buffer+pbSize); // end position - } - - 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; - num = read (fd, buffer+pbSize, bufSize); - if (num <= 0) { - // ERROR or EOF - return EOF; - } - - // 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 fdistream : public std::istream { - protected: - fdinbuf buf; - public: - fdistream (int fd) : std::istream(0), buf(fd) { - rdbuf(&buf); - } -}; - - -} // END namespace boost - -#endif /*BOOST_FDSTREAM_HPP*/ diff --git a/format.cc b/format.cc deleted file mode 100644 index 6a378823..00000000 --- a/format.cc +++ /dev/null @@ -1,239 +0,0 @@ -#include "format.h" -#if 0 -#ifdef USE_BOOST_PYTHON -#include "py_eval.h" -#endif -#endif - -namespace ledger { - -void format_t::parse(const string& fmt) -{ - element_t * current = NULL; - - char buf[1024]; - char * q = buf; - - if (elements.size() > 0) - clear_elements(); - format_string = fmt; - - for (const char * p = fmt.c_str(); *p; p++) { - if (*p != '%' && *p != '\\') { - *q++ = *p; - continue; - } - else if (*p == '\\') { - p++; - switch (*p) { - case 'b': *q++ = '\b'; break; - case 'f': *q++ = '\f'; break; - case 'n': *q++ = '\n'; break; - case 'r': *q++ = '\r'; break; - case 't': *q++ = '\t'; break; - case 'v': *q++ = '\v'; break; - default: - *q++ = *p; - break; - } - continue; - } - else { - assert(*p == '%'); - if (*(p + 1) == '%') { - p++; // %% is the same as \% - *q++ = *p; - continue; - } - } - - current = new element_t; - elements.push_back(current); - - if (q != buf) { - current->kind = element_t::TEXT; - current->chars = new string(buf, q); - q = buf; - - current = new element_t; - elements.push_back(current); - } - - ++p; - if (*p == '-') { - current->align_left = true; - ++p; - } - - if (*p && std::isdigit(*p)) { - int num = *p++ - '0'; - while (*p && std::isdigit(*p)) { - num *= 10; - num += *p++ - '0'; - } - current->min_width = num; - } - - if (*p == '.') { - ++p; - int num = 0; - while (*p && std::isdigit(*p)) { - num *= 10; - num += *p++ - '0'; - } - - current->max_width = num; - if (current->min_width == -1) - current->min_width = current->max_width; - } - - if (current->max_width != -1 && current->min_width != -1 && - current->max_width < current->min_width) - throw_(format_exception, "Maximum width is less than the minimum width"); - - switch (*p) { - case '|': - current->kind = element_t::COLUMN; - break; - - case '{': - case '(': { - char open = *p; - char close = *p == '{' ? '}' : ')'; - ++p; - const char * b = p; - int depth = 1; - while (*p) { - if (*p == close && --depth == 0) - break; - else if (*p == open) - ++depth; - p++; - } - if (*p != close) - throw_(format_exception, "Missing '" << close << "'"); - - if (open == '{') { - assert(! current->xpath); - current->kind = element_t::XPATH; - current->xpath = new xml::xpath_t(string(b, p)); - } else { - assert(! current->format); - current->kind = element_t::GROUP; - current->format = new format_t(string(b, p)); - } - break; - } - - default: - assert(! current->xpath); - current->kind = element_t::XPATH; - current->xpath = new xml::xpath_t(string(p, p + 1)); - break; - } - } - - if (q != buf) { - current = new element_t; - elements.push_back(current); - - current->kind = element_t::TEXT; - current->chars = new string(buf, q); - } -} - -void format_t::compile(xml::node_t * context) -{ - for (std::list::iterator i = elements.begin(); - i != elements.end(); - i++) - switch ((*i)->kind) { - case element_t::XPATH: - assert((*i)->xpath); - (*i)->xpath->compile(context); - break; - case element_t::GROUP: - assert((*i)->format); - (*i)->format->compile(context); - break; - default: - break; - } -} - -int format_t::element_formatter_t::operator() - (std::ostream& out_str, element_t * elem, xml::node_t * context, - int column) const -{ - if (elem->kind == element_t::COLUMN) { - if (elem->max_width != -1 && elem->max_width < column) { - out_str << '\n'; - column = 0; - } - - if (elem->min_width != -1 && elem->min_width > column) { - out_str << string(elem->min_width - column, ' '); - column = elem->min_width; - } - return column; - } - - std::ostringstream out; - - if (elem->align_left) - out << std::left; - else - out << std::right; - - if (elem->min_width > 0) - out.width(elem->min_width); - - int start_column = column; - - if (elem->kind == element_t::XPATH) - elem->xpath->calc(context).strip_annotations() - .write(out, elem->min_width, elem->max_width); - else if (elem->kind == element_t::GROUP) - column = elem->format->format(out, context, column); - else if (elem->kind == element_t::TEXT) - out << *elem->chars; - else - assert(0); - - string temp = out.str(); - for (string::const_iterator i = temp.begin(); - i != temp.end(); - i++) - if (*i == '\n' || *i == '\r') - column = 0; - else - column++; - - int virtual_width = column - start_column; - - if (elem->min_width != -1 && virtual_width < elem->min_width) { - out_str << temp << string(' ', elem->min_width - virtual_width); - } - else if (elem->max_width != -1 && virtual_width > elem->max_width) { - temp.erase(temp.length() - (virtual_width - elem->max_width)); - out_str << temp; - } - else { - out_str << temp; - } - - return column; -} - -int format_t::format(std::ostream& out, xml::node_t * context, - int column, const element_formatter_t& formatter) const -{ - for (std::list::const_iterator i = elements.begin(); - i != elements.end(); - i++) - column = formatter(out, *i, context, column); - - return column; -} - -} // namespace ledger diff --git a/format.h b/format.h deleted file mode 100644 index 1ddd8202..00000000 --- a/format.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef _FORMAT_H -#define _FORMAT_H - -#include "xpath.h" - -namespace ledger { - -class format_t -{ - public: - struct element_t - { - bool align_left; - short min_width; - short max_width; - - enum kind_t { UNKNOWN, TEXT, COLUMN, XPATH, GROUP } kind; - union { - string * chars; - xml::xpath_t * xpath; - format_t * format; - }; - - element_t() - : align_left(false), min_width(-1), max_width(-1), - kind(UNKNOWN), chars(NULL) { - TRACE_CTOR(element_t, ""); - } - - ~element_t() { - TRACE_DTOR(element_t); - - switch (kind) { - case TEXT: - delete chars; - break; - case XPATH: - delete xpath; - break; - case GROUP: - delete format; - break; - default: - assert(! chars); - break; - } - } - - private: - element_t(const element_t& other); - }; - - struct element_formatter_t { - virtual ~element_formatter_t() {} - virtual int operator()(std::ostream& out, element_t * element, - xml::node_t * context, int column) const; - }; - - string format_string; - std::list elements; - - private: - format_t(const format_t&); - - public: - format_t() { - TRACE_CTOR(format_t, ""); - } - format_t(const string& fmt) { - TRACE_CTOR(format_t, "const string&"); - parse(fmt); - } - - void clear_elements() { - for (std::list::iterator i = elements.begin(); - i != elements.end(); - i++) - delete *i; - elements.clear(); - } - - virtual ~format_t() { - TRACE_DTOR(format_t); - clear_elements(); - } - - void parse(const string& fmt); - - void compile(const string& fmt, xml::node_t * context = NULL) { - parse(fmt); - compile(context); - } - void compile(xml::node_t * context = NULL); - - int format(std::ostream& out, xml::node_t * context = NULL, - int column = 0, const element_formatter_t& formatter = - element_formatter_t()) const; - - operator bool() const { - return ! format_string.empty(); - } -}; - -DECLARE_EXCEPTION(format_exception); - -} // namespace ledger - -#endif // _FORMAT_H diff --git a/gdtoa/gd_qnan.h b/gdtoa/gd_qnan.h deleted file mode 100644 index 87eba8fb..00000000 --- a/gdtoa/gd_qnan.h +++ /dev/null @@ -1,12 +0,0 @@ -#define f_QNAN 0xffc00000 -#define d_QNAN0 0x0 -#define d_QNAN1 0xfff80000 -#define ld_QNAN0 0x0 -#define ld_QNAN1 0xc0000000 -#define ld_QNAN2 0xffff -#define ld_QNAN3 0x0 -#define ldus_QNAN0 0x0 -#define ldus_QNAN1 0x0 -#define ldus_QNAN2 0x0 -#define ldus_QNAN3 0xc000 -#define ldus_QNAN4 0xffff diff --git a/gnucash.cc b/gnucash.cc deleted file mode 100644 index abe8c555..00000000 --- a/gnucash.cc +++ /dev/null @@ -1,366 +0,0 @@ -#include "gnucash.h" - -namespace ledger { - -void startElement(void *userData, const char *name, const char ** /* attrs */) -{ - gnucash_parser_t * parser = static_cast(userData); - - if (std::strcmp(name, "gnc:account") == 0) { - parser->curr_account = new account_t(parser->master_account); - } - else if (std::strcmp(name, "act:name") == 0) - parser->action = gnucash_parser_t::ACCOUNT_NAME; - else if (std::strcmp(name, "act:id") == 0) - parser->action = gnucash_parser_t::ACCOUNT_ID; - else if (std::strcmp(name, "act:parent") == 0) - parser->action = gnucash_parser_t::ACCOUNT_PARENT; - else if (std::strcmp(name, "gnc:commodity") == 0) - parser->curr_comm = NULL; - else if (std::strcmp(name, "cmdty:id") == 0) - parser->action = gnucash_parser_t::COMM_SYM; - else if (std::strcmp(name, "cmdty:name") == 0) - parser->action = gnucash_parser_t::COMM_NAME; - else if (std::strcmp(name, "cmdty:fraction") == 0) - parser->action = gnucash_parser_t::COMM_PREC; - else if (std::strcmp(name, "gnc:transaction") == 0) { - assert(! parser->curr_entry); - parser->curr_entry = new entry_t; - } - else if (std::strcmp(name, "trn:num") == 0) - parser->action = gnucash_parser_t::ENTRY_NUM; - else if (std::strcmp(name, "trn:date-posted") == 0) - parser->action = gnucash_parser_t::ALMOST_ENTRY_DATE; - else if (parser->action == gnucash_parser_t::ALMOST_ENTRY_DATE && - std::strcmp(name, "ts:date") == 0) - parser->action = gnucash_parser_t::ENTRY_DATE; - else if (std::strcmp(name, "trn:description") == 0) - parser->action = gnucash_parser_t::ENTRY_DESC; - else if (std::strcmp(name, "trn:split") == 0) { - assert(parser->curr_entry); - parser->curr_entry->add_transaction(new transaction_t(parser->curr_account)); - } - else if (std::strcmp(name, "split:reconciled-state") == 0) - parser->action = gnucash_parser_t::XACT_STATE; - else if (std::strcmp(name, "split:amount") == 0) - parser->action = gnucash_parser_t::XACT_AMOUNT; - else if (std::strcmp(name, "split:value") == 0) - parser->action = gnucash_parser_t::XACT_VALUE; - else if (std::strcmp(name, "split:quantity") == 0) - parser->action = gnucash_parser_t::XACT_QUANTITY; - else if (std::strcmp(name, "split:account") == 0) - parser->action = gnucash_parser_t::XACT_ACCOUNT; - else if (std::strcmp(name, "split:memo") == 0) - parser->action = gnucash_parser_t::XACT_NOTE; -} - -void endElement(void *userData, const char *name) -{ - gnucash_parser_t * parser = static_cast(userData); - - if (std::strcmp(name, "gnc:account") == 0) { - assert(parser->curr_account); - if (parser->curr_account->parent == parser->master_account) - parser->curr_journal->add_account(parser->curr_account); - parser->accounts_by_id.insert(accounts_pair(parser->curr_account_id, - parser->curr_account)); - parser->curr_account = NULL; - } - else if (std::strcmp(name, "gnc:commodity") == 0) { - parser->curr_comm = NULL; - } - else if (std::strcmp(name, "gnc:transaction") == 0) { - assert(parser->curr_entry); - - // Add the new entry (what gnucash calls a 'transaction') to the - // journal - if (! parser->curr_journal->add_entry(parser->curr_entry)) { - print_entry(std::cerr, *parser->curr_entry); - parser->have_error = "The above entry does not balance"; - delete parser->curr_entry; - } else { - parser->curr_entry->src_idx = parser->src_idx; - parser->curr_entry->beg_pos = parser->beg_pos; - parser->curr_entry->beg_line = parser->beg_line; - parser->curr_entry->end_pos = parser->instreamp->tellg(); - parser->curr_entry->end_line = - XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; - parser->count++; - } - - // Clear the relevant variables for the next run - parser->curr_entry = NULL; - parser->entry_comm = NULL; - } - else if (std::strcmp(name, "trn:split") == 0) { - transaction_t * xact = parser->curr_entry->transactions.back(); - - // Identify the commodity to use for the value of this - // transaction. The quantity indicates how many times that value - // the transaction is worth. - amount_t value; - commodity_t * default_commodity = NULL; - if (parser->entry_comm) { - default_commodity = parser->entry_comm; - } else { - gnucash_parser_t::account_comm_map::iterator ac = - parser->account_comms.find(xact->account); - if (ac != parser->account_comms.end()) - default_commodity = (*ac).second; - } - - if (default_commodity) { - parser->curr_quant.set_commodity(*default_commodity); - value = parser->curr_quant.round(); - - if (parser->curr_value.commodity() == *default_commodity) - parser->curr_value = value; - } else { - value = parser->curr_quant; - } - - xact->state = parser->curr_state; - xact->amount = value; - if (value != parser->curr_value) - xact->cost = new amount_t(parser->curr_value); - - xact->beg_pos = parser->beg_pos; - xact->beg_line = parser->beg_line; - xact->end_pos = parser->instreamp->tellg(); - xact->end_line = - XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; - - // Clear the relevant variables for the next run - parser->curr_state = transaction_t::UNCLEARED; - parser->curr_value = amount_t(); - parser->curr_quant = amount_t(); - } - - parser->action = gnucash_parser_t::NO_ACTION; -} - -amount_t gnucash_parser_t::convert_number(const string& number, - int * precision) -{ - const char * num = number.c_str(); - - if (char * p = std::strchr(num, '/')) { - string numer_str(num, p - num); - string denom_str(p + 1); - - amount_t amt(numer_str); - amount_t den(denom_str); - - if (precision) - *precision = denom_str.length() - 1; - - if (! den) { - have_error = "Denominator in entry is zero!"; - return amt; - } else { - return amt / den; - } - } else { - return amount_t(number); - } -} - -void dataHandler(void *userData, const char *s, int len) -{ - gnucash_parser_t * parser = static_cast(userData); - - switch (parser->action) { - case gnucash_parser_t::ACCOUNT_NAME: - parser->curr_account->name = string(s, len); - break; - - case gnucash_parser_t::ACCOUNT_ID: - parser->curr_account_id = string(s, len); - break; - - case gnucash_parser_t::ACCOUNT_PARENT: { - accounts_map::iterator i = parser->accounts_by_id.find(string(s, len)); - assert(i != parser->accounts_by_id.end()); - parser->curr_account->parent = (*i).second; - parser->curr_account->depth = parser->curr_account->parent->depth + 1; - (*i).second->add_account(parser->curr_account); - break; - } - - case gnucash_parser_t::COMM_SYM: { - string symbol(s, len); - if (symbol == "USD") symbol = "$"; - - parser->curr_comm = commodity_t::find_or_create(symbol); - assert(parser->curr_comm); - - if (symbol != "$") - parser->curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); - - if (parser->curr_account) - parser->account_comms.insert - (gnucash_parser_t::account_comm_pair(parser->curr_account, - parser->curr_comm)); - else if (parser->curr_entry) - parser->entry_comm = parser->curr_comm; - break; - } - - case gnucash_parser_t::COMM_NAME: - parser->curr_comm->set_name(string(s, len)); - break; - - case gnucash_parser_t::COMM_PREC: - parser->curr_comm->set_precision(len - 1); - break; - - case gnucash_parser_t::ENTRY_NUM: - parser->curr_entry->code = string(s, len); - break; - - case gnucash_parser_t::ENTRY_DATE: - parser->curr_entry->_date = parse_datetime(string(s, len)); - break; - - case gnucash_parser_t::ENTRY_DESC: - parser->curr_entry->payee = string(s, len); - break; - - case gnucash_parser_t::XACT_STATE: - if (*s == 'y') - parser->curr_state = transaction_t::CLEARED; - else if (*s == 'n') - parser->curr_state = transaction_t::UNCLEARED; - else - parser->curr_state = transaction_t::PENDING; - break; - - case gnucash_parser_t::XACT_VALUE: { - int precision; - assert(parser->entry_comm); - parser->curr_value = parser->convert_number(string(s, len), &precision); - parser->curr_value.set_commodity(*parser->entry_comm); - - if (precision > parser->entry_comm->precision()) - parser->entry_comm->set_precision(precision); - break; - } - - case gnucash_parser_t::XACT_QUANTITY: - parser->curr_quant = parser->convert_number(string(s, len)); - break; - - case gnucash_parser_t::XACT_ACCOUNT: { - transaction_t * xact = parser->curr_entry->transactions.back(); - - accounts_map::iterator i = - parser->accounts_by_id.find(string(s, len)); - if (i != parser->accounts_by_id.end()) { - xact->account = (*i).second; - } else { - xact->account = parser->curr_journal->find_account(""); - - parser->have_error = (string("Could not find account ") + - string(s, len)); - } - break; - } - - case gnucash_parser_t::XACT_NOTE: - parser->curr_entry->transactions.back()->note = string(s, len); - break; - - case gnucash_parser_t::NO_ACTION: - case gnucash_parser_t::ALMOST_ENTRY_DATE: - case gnucash_parser_t::XACT_AMOUNT: - break; - - default: - assert(0); - break; - } -} - -bool gnucash_parser_t::test(std::istream& in) const -{ - char buf[5]; - in.read(buf, 5); - in.clear(); - in.seekg(0, std::ios::beg); - - return std::strncmp(buf, "master; - curr_account = NULL; - curr_entry = NULL; - curr_comm = NULL; - entry_comm = NULL; - curr_state = transaction_t::UNCLEARED; - - instreamp = ∈ - path = original_file ? *original_file : ""; - 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("$"); - usd->set_precision(2); - usd->add_flags(COMMODITY_STYLE_THOUSANDS); - - offset = 2; - expat_parser = XML_ParserCreate(NULL); - - XML_SetElementHandler(parser, startElement, endElement); - XML_SetCharacterDataHandler(parser, dataHandler); - XML_SetUserData(parser, this); - - while (in.good() && ! in.eof()) { - beg_pos = in.tellg(); - beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1; - - 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++; - const char * msg = XML_ErrorString(XML_GetErrorCode(parser)); - XML_ParserFree(parser); - throw_(parse_exception, msg); - } - - if (! have_error.empty()) { - //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; -#if 0 - // jww (2007-04-26): What is this doing? - parse_error err(have_error); - std::cerr << "Error: " << err.what() << std::endl; -#endif - have_error = ""; - } - } - - XML_ParserFree(parser); - - accounts_by_id.clear(); - curr_account_id.clear(); - - return count; -} - -} // namespace ledger diff --git a/gnucash.h b/gnucash.h deleted file mode 100644 index a0d9fb18..00000000 --- a/gnucash.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef _GNUCASH_H -#define _GNUCASH_H - -#include "parser.h" -#include "journal.h" - -namespace ledger { - -struct gnucash_parser_t : public parser_t -{ - typedef std::map accounts_map; - typedef std::pair accounts_pair; - - typedef std::map account_comm_map; - typedef std::pair account_comm_pair; - - journal_t * curr_journal; - account_t * master_account; - account_t * curr_account; - string curr_account_id; - entry_t * curr_entry; - commodity_t * entry_comm; - commodity_t * curr_comm; - amount_t curr_value; - amount_t curr_quant; - XML_Parser expat_parser; - accounts_map accounts_by_id; - account_comm_map account_comms; - unsigned int count; - string have_error; - - std::istream * instreamp; - unsigned int offset; - XML_Parser parser; - string path; - unsigned int src_idx; - unsigned long beg_pos; - unsigned long beg_line; - - transaction_t::state_t curr_state; - - enum action_t { - NO_ACTION, - ACCOUNT_NAME, - ACCOUNT_ID, - ACCOUNT_PARENT, - COMM_SYM, - COMM_NAME, - COMM_PREC, - ENTRY_NUM, - ALMOST_ENTRY_DATE, - ENTRY_DATE, - ENTRY_DESC, - XACT_STATE, - XACT_AMOUNT, - XACT_VALUE, - XACT_QUANTITY, - XACT_ACCOUNT, - XACT_NOTE - } action; - - public: - virtual bool test(std::istream& in) const; - - virtual unsigned int parse(std::istream& in, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); - - amount_t convert_number(const string& number, int * precision = NULL); -}; - -} // namespace ledger - -#endif // _GNUCASH_H diff --git a/journal.cc b/journal.cc deleted file mode 100644 index 1ae4be80..00000000 --- a/journal.cc +++ /dev/null @@ -1,667 +0,0 @@ -#include "journal.h" -#include "mask.h" -#if 0 -#ifdef USE_BOOST_PYTHON -#include "py_eval.h" -#endif -#endif - -namespace ledger { - -const string version = PACKAGE_VERSION; - -bool transaction_t::use_effective_date = false; - -transaction_t::~transaction_t() -{ - TRACE_DTOR(transaction_t); - if (cost) delete cost; -} - -moment_t transaction_t::actual_date() const -{ - if (! is_valid_moment(_date) && entry) - return entry->actual_date(); - return _date; -} - -moment_t transaction_t::effective_date() const -{ - if (! is_valid_moment(_date_eff) && entry) - return entry->effective_date(); - return _date_eff; -} - -bool transaction_t::valid() const -{ - if (! entry) { - DEBUG_("ledger.validate", "transaction_t: ! entry"); - return false; - } - - if (state != UNCLEARED && state != CLEARED && state != PENDING) { - DEBUG_("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_("ledger.validate", "transaction_t: ! found"); - return false; - } - - if (! account) { - DEBUG_("ledger.validate", "transaction_t: ! account"); - return false; - } - - if (! amount.valid()) { - DEBUG_("ledger.validate", "transaction_t: ! amount.valid()"); - return false; - } - - if (cost && ! cost->valid()) { - DEBUG_("ledger.validate", "transaction_t: cost && ! cost->valid()"); - return false; - } - - if (flags & ~0x003f) { - DEBUG_("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 - ((*x)->amount.commodity())); - if (ann_comm.price) - balance += ann_comm.price * (*x)->amount.number() - *((*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++; - - amount_t per_unit_cost = - amount_t((*other_bal).second / (*this_bal).second.number()).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(this); - - if ((*x)->amount.commodity() && - ! (*x)->amount.commodity().annotated) - (*x)->amount.annotate_commodity - (per_unit_cost.abs(), - entry ? entry->actual_date() : moment_t(), - entry ? entry->code : ""); - - (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount.number())); - 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_(exception, "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.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)->negate(); - (*x)->flags |= TRANSACTION_CALCULATED; - - balance += (*x)->amount; - break; - - default: - break; - } - } - - if (balance) { -#if 1 - throw_(balance_exception, "Entry does not balance"); -#else - 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 - } - - 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), data(NULL) -{ - TRACE_CTOR(entry_t, "copy"); - 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 (! is_valid_moment(_date) || ! journal) { - DEBUG_("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_("ledger.validate", "entry_t: transaction not valid"); - return false; - } - - return true; -} - -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++) { - // jww (2006-09-10): Create a scope here based on entry - if (predicate.calc((xml::node_t *) NULL)) { - 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; - 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); - entry.add_transaction(xact); - } - } - } -} - -account_t::~account_t() -{ - TRACE_DTOR(account_t); - - for (accounts_map::iterator i = accounts.begin(); - i != accounts.end(); - i++) - delete (*i).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); - account->journal = journal; - - std::pair 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 string& regexp) -{ - return find_account_re_(master, mask_t(regexp)); -} - -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; -} - -bool account_t::valid() const -{ - if (depth > 256 || ! journal) { - DEBUG_("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_("ledger.validate", "account_t: parent refers to itself!"); - return false; - } - - if (! (*i).second->valid()) { - DEBUG_("ledger.validate", "account_t: child not valid"); - return false; - } - } - - return true; -} - -journal_t::~journal_t() -{ - TRACE_DTOR(journal_t); - - assert(master); - delete master; - - if (document) - delete document; - - // 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.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; - } - - for (entries_list::const_iterator i = entries.begin(); - i != entries.end(); - i++) - if (! (*i)->valid()) { - DEBUG_("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_("ledger.validate", "journal_t: commodity not valid"); - return false; - } - - return true; -} - -void print_entry(std::ostream& out, const entry_base_t& entry_base, - const string& prefix) -{ - string print_format; - - if (dynamic_cast(&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(&entry_base)) { - out << "= " << entry->predicate.expr << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else if (const period_entry_t * entry = - dynamic_cast(&entry_base)) { - out << "~ " << entry->period_string << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else { - assert(0); - } - -#if 0 - format_entries formatter(out, print_format); - walk_transactions(const_cast(entry_base.transactions), - formatter); - formatter.flush(); - - clear_transaction_xdata cleaner; - walk_transactions(const_cast(entry_base.transactions), - cleaner); -#endif -} - -#if 0 -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 string& desc) throw() - : file_context("", 0, desc), xact(_xact) -{ - const ledger::strings_list& sources(xact.entry->journal->sources); - unsigned 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; -} -#endif - -} // namespace ledger diff --git a/journal.h b/journal.h deleted file mode 100644 index 1995e0f3..00000000 --- a/journal.h +++ /dev/null @@ -1,471 +0,0 @@ -#ifndef _JOURNAL_H -#define _JOURNAL_H - -#include "xpath.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; - moment_t _date; - moment_t _date_eff; - account_t * account; - amount_t amount; - string amount_expr; - amount_t * cost; - string cost_expr; - state_t state; - unsigned short flags; - string note; - unsigned long beg_pos; - unsigned long beg_line; - unsigned long 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) { - TRACE_CTOR(transaction_t, "account_t *"); - } - transaction_t(account_t * _account, - const amount_t& _amount, - unsigned int _flags = TRANSACTION_NORMAL, - const 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) { - TRACE_CTOR(transaction_t, "account_t *, const amount_t&, unsigned int, const string&"); - } - 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) { - TRACE_CTOR(transaction_t, "copy"); - } - ~transaction_t(); - - moment_t actual_date() const; - moment_t effective_date() const; - moment_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; -}; - -#if 0 -class xact_context : public file_context { - public: - const transaction_t& xact; - - xact_context(const transaction_t& _xact, - const string& desc = "") throw(); - virtual ~xact_context() throw() {} -}; -#endif - -class journal_t; - -typedef std::list transactions_list; - -class entry_base_t -{ - public: - journal_t * journal; - unsigned long src_idx; - unsigned long beg_pos; - unsigned long beg_line; - unsigned long 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) { - TRACE_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) - { - TRACE_CTOR(entry_base_t, "copy"); - 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() { - TRACE_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: - moment_t _date; - moment_t _date_eff; - string code; - string payee; - - mutable void * data; - - entry_t() : data(NULL) { - TRACE_CTOR(entry_t, ""); - } - entry_t(const entry_t& e); - - virtual ~entry_t() { - TRACE_DTOR(entry_t); - } - - moment_t actual_date() const { - return _date; - } - moment_t effective_date() const { - if (! is_valid_moment(_date_eff)) - return _date; - return _date_eff; - } - moment_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; -}; - -void print_entry(std::ostream& out, const entry_base_t& entry, - const string& prefix = ""); - -#if 0 -class entry_context : public error_context { - public: - const entry_base_t& entry; - - entry_context(const entry_base_t& _entry, - const string& _desc = "") throw() - : error_context(_desc), entry(_entry) {} - virtual ~entry_context() throw() {} - - virtual void describe(std::ostream& out) const throw(); -}; -#endif - -DECLARE_EXCEPTION(balance_exception); - - -class auto_entry_t : public entry_base_t -{ -public: - xml::xpath_t predicate; - - auto_entry_t() { - TRACE_CTOR(auto_entry_t, ""); - } - 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_t * _journal) : journal(_journal) {} - 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 string& _period) - : period(_period), period_string(_period) { - TRACE_CTOR(period_entry_t, "const string&"); - } - 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"); - } - - virtual ~period_entry_t() { - TRACE_DTOR(period_entry_t); - } - - virtual bool valid() const { - return period; - } -}; - - -typedef std::map accounts_map; -typedef std::pair accounts_pair; - -class account_t -{ - public: - typedef unsigned long ident_t; - - journal_t * journal; - account_t * parent; - string name; - 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 string& _note = "") - : 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(); - - bool operator==(const account_t& account) { - return this == &account; - } - bool operator!=(const account_t& account) { - return ! (*this == account); - } - - 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 string& name, bool auto_create = true); - - operator 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) : - entry_finalizer_t(), func(other.func) {} - virtual bool operator()(entry_t& entry, bool post) { - return func(entry, post); - } -}; - -template -void add_hook(std::list& list, T obj, const bool prepend = false) { - if (prepend) - list.push_front(obj); - else - list.push_back(obj); -} - -template -void remove_hook(std::list& list, T obj) { - list.remove(obj); -} - -template -bool run_hooks(std::list& list, Data& item, bool post) { - for (typename std::list::const_iterator i = list.begin(); - i != list.end(); - i++) - if (! (*(*i))(item, post)) - return false; - return true; -} - - -typedef std::list entries_list; -typedef std::list auto_entries_list; -typedef std::list period_entries_list; -typedef std::list strings_list; - -class session_t; - -class journal_t -{ - public: - session_t * session; - account_t * master; - account_t * basket; - entries_list entries; - strings_list sources; - string price_db; - char * item_pool; - char * item_pool_end; - - // This is used for dynamically representing the journal data as an - // XML tree, to facilitate transformations without modifying any of - // the underlying structures (the transformers modify the XML tree - // -- perhaps even adding, changing or deleting nodes -- but they do - // not affect the basic data parsed from the journal file). - xml::document_t * document; - - auto_entries_list auto_entries; - period_entries_list period_entries; - mutable accounts_map accounts_cache; - - std::list entry_finalize_hooks; - - journal_t(session_t * _session) - : session(_session), basket(NULL), - item_pool(NULL), item_pool_end(NULL), document(NULL) { - TRACE_CTOR(journal_t, ""); - master = new account_t(NULL, ""); - master->journal = this; - } - ~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 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 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_finalize_hooks, finalizer); - } - void remove_entry_finalizer(entry_finalizer_t * finalizer) { - remove_hook(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 string version; - -} // namespace ledger - -#endif // _JOURNAL_H diff --git a/ledger.el b/ledger.el deleted file mode 100644 index 0812f8f6..00000000 --- a/ledger.el +++ /dev/null @@ -1,1256 +0,0 @@ -;;; ledger.el --- Helper code for use with the "ledger" command-line tool - -;; Copyright (C) 2007 John Wiegley (johnw AT gnu DOT org) - -;; Emacs Lisp Archive Entry -;; Filename: ledger.el -;; Version: 3.0 -;; Date: Thu 16-Apr-2007 -;; Keywords: data -;; Author: John Wiegley (johnw AT gnu DOT org) -;; Maintainer: John Wiegley (johnw AT gnu DOT org) -;; Description: Helper code for using my "ledger" command-line tool -;; URL: http://www.newartisans.com/johnw/emacs.html -;; Compatibility: Emacs22 - -;; This file is not part of GNU Emacs. - -;; This is free software; you can redistribute it and/or modify it under -;; the terms of the GNU General Public License as published by the Free -;; Software Foundation; either version 2, or (at your option) any later -;; version. -;; -;; This is distributed in the hope that it will be useful, but WITHOUT -;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -;; for more details. -;; -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, -;; MA 02111-1307, USA. - -;;; Commentary: - -;; To use this module: Load this file, open a ledger data file, and -;; type M-x ledger-mode. Once this is done, you can type: -;; -;; C-c C-a add a new entry, based on previous entries -;; C-c C-y set default year for entry mode -;; C-c C-m set default month for entry mode -;; C-c C-r reconcile uncleared entries related to an account -;; C-c C-o C-r run a ledger report -;; C-C C-o C-g goto the ledger report buffer -;; C-c C-o C-e edit the defined ledger reports -;; C-c C-o C-s save a report definition based on the current report -;; C-c C-o C-a rerun a ledger report -;; C-c C-o C-k kill the ledger report buffer -;; -;; In the reconcile buffer, use SPACE to toggle the cleared status of -;; a transaction, C-x C-s to save changes (to the ledger file as -;; well), or C-c C-r to attempt an auto-reconcilation based on the -;; statement's ending date and balance. -;; -;; The ledger reports command asks the user to select a report to run -;; then creates a report buffer containing the results of running the -;; associated command line. Its' behavior is modified by a prefix -;; argument which, when given, causes the generated command line that -;; will be used to create the report to be presented for editing -;; before the report is actually run. Arbitrary unnamed command lines -;; can be run by specifying an empty name for the report. The command -;; line used can later be named and saved for future use as a named -;; report from the generated reports buffer. -;; -;; In a report buffer, the following keys are available: -;; (space) scroll up -;; e edit the defined ledger reports -;; s save a report definition based on the current report -;; q quit the report (return to ledger buffer) -;; r redo the report -;; k kill the report buffer - -(require 'esh-util) -(require 'esh-arg) -(require 'pcomplete) - -(defvar ledger-version "1.2" - "The version of ledger.el currently loaded") - -(defgroup ledger nil - "Interface to the Ledger command-line accounting program." - :group 'data) - -(defcustom ledger-binary-path (executable-find "ledger") - "Path to the ledger executable." - :type 'file - :group 'ledger) - -(defcustom ledger-clear-whole-entries nil - "If non-nil, clear whole entries, not individual transactions." - :type 'boolean - :group 'ledger) - -(defcustom ledger-reports - '(("bal" "ledger -f %(ledger-file) bal") - ("reg" "ledger -f %(ledger-file) reg") - ("payee" "ledger -f %(ledger-file) reg -- %(payee)") - ("account" "ledger -f %(ledger-file) reg %(account)")) - "Definition of reports to run. - -Each element has the form (NAME CMDLINE). The command line can -contain format specifiers that are replaced with context sensitive -information. Format specifiers have the format '%()' where - is an identifier for the information to be replaced. The -`ledger-report-format-specifiers' alist variable contains a mapping -from format specifier identifier to a lisp function that implements -the substitution. See the documentation of the individual functions -in that variable for more information on the behavior of each -specifier." - :type '(repeat (list (string :tag "Report Name") - (string :tag "Command Line"))) - :group 'ledger) - -(defcustom ledger-report-format-specifiers - '(("ledger-file" . ledger-report-ledger-file-format-specifier) - ("payee" . ledger-report-payee-format-specifier) - ("account" . ledger-report-account-format-specifier)) - "Alist mapping ledger report format specifiers to implementing functions - -The function is called with no parameters and expected to return the -text that should replace the format specifier." - :type 'alist - :group 'ledger) - -(defcustom ledger-default-acct-transaction-indent " " - "Default indentation for account transactions in an entry." - :type 'string - :group 'ledger) - -(defvar bold 'bold) -(defvar ledger-font-lock-keywords - `((,(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" - "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") 3 bold) - ("^\\s-+.+?\\( \\|\t\\|\\s-+$\\)" . font-lock-keyword-face)) - "Default expressions to highlight in Ledger mode.") - -(defsubst ledger-current-year () - (format-time-string "%Y")) -(defsubst ledger-current-month () - (format-time-string "%m")) - -(defvar ledger-year (ledger-current-year) - "Start a ledger session with the current year, but make it -customizable to ease retro-entry.") -(defvar ledger-month (ledger-current-month) - "Start a ledger session with the current month, but make it -customizable to ease retro-entry.") - -(defun ledger-iterate-entries (callback) - (goto-char (point-min)) - (let* ((now (current-time)) - (current-year (nth 5 (decode-time now)))) - (while (not (eobp)) - (when (looking-at - (concat "\\(Y\\s-+\\([0-9]+\\)\\|" - "\\([0-9]\\{4\\}+\\)?[./]?" - "\\([0-9]+\\)[./]\\([0-9]+\\)\\s-+" - "\\(\\*\\s-+\\)?\\(.+\\)\\)")) - (let ((found (match-string 2))) - (if found - (setq current-year (string-to-number found)) - (let ((start (match-beginning 0)) - (year (match-string 3)) - (month (string-to-number (match-string 4))) - (day (string-to-number (match-string 5))) - (mark (match-string 6)) - (desc (match-string 7))) - (if (and year (> (length year) 0)) - (setq year (string-to-number year))) - (funcall callback start - (encode-time 0 0 0 day month - (or year current-year)) - mark desc))))) - (forward-line)))) - -(defun ledger-time-less-p (t1 t2) - "Say whether time value T1 is less than time value T2." - (or (< (car t1) (car t2)) - (and (= (car t1) (car t2)) - (< (nth 1 t1) (nth 1 t2))))) - -(defun ledger-time-subtract (t1 t2) - "Subtract two time values. -Return the difference in the format of a time value." - (let ((borrow (< (cadr t1) (cadr t2)))) - (list (- (car t1) (car t2) (if borrow 1 0)) - (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2))))) - -(defun ledger-find-slot (moment) - (catch 'found - (ledger-iterate-entries - (function - (lambda (start date mark desc) - (if (ledger-time-less-p moment date) - (throw 'found t))))))) - -(defun ledger-add-entry (entry-text) - (interactive - (list - (read-string "Entry: " (concat ledger-year "/" ledger-month "/")))) - (let* ((args (with-temp-buffer - (insert entry-text) - (eshell-parse-arguments (point-min) (point-max)))) - (date (car args)) - (insert-year t) - (ledger-buf (current-buffer)) - exit-code) - (if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date) - (setq date - (encode-time 0 0 0 (string-to-number (match-string 3 date)) - (string-to-number (match-string 2 date)) - (string-to-number (match-string 1 date))))) - (ledger-find-slot date) - (save-excursion - (if (re-search-backward "^Y " nil t) - (setq insert-year nil))) - (save-excursion - (insert - (with-temp-buffer - (setq exit-code - (apply #'ledger-run-ledger ledger-buf "entry" - (mapcar 'eval args))) - (if (= 0 exit-code) - (if insert-year - (buffer-substring 2 (point-max)) - (buffer-substring 7 (point-max))) - (concat (if insert-year entry-text - (substring entry-text 6)) "\n"))) "\n")))) - -(defun ledger-current-entry-bounds () - (save-excursion - (when (or (looking-at "^[0-9]") - (re-search-backward "^[0-9]" nil t)) - (let ((beg (point))) - (while (not (eolp)) - (forward-line)) - (cons (copy-marker beg) (point-marker)))))) - -(defun ledger-delete-current-entry () - (interactive) - (let ((bounds (ledger-current-entry-bounds))) - (delete-region (car bounds) (cdr bounds)))) - -(defun ledger-toggle-current-entry (&optional style) - (interactive) - (let (clear) - (save-excursion - (when (or (looking-at "^[0-9]") - (re-search-backward "^[0-9]" nil t)) - (skip-chars-forward "0-9./=") - (delete-horizontal-space) - (if (member (char-after) '(?\* ?\!)) - (progn - (delete-char 1) - (if (and style (eq style 'cleared)) - (insert " *"))) - (if (and style (eq style 'pending)) - (insert " ! ") - (insert " * ")) - (setq clear t)))) - clear)) - -(defun ledger-toggle-current-transaction (&optional style) - "Toggle the cleared status of the transaction under point. -Optional argument STYLE may be `pending' or `cleared', depending -on which type of status the caller wishes to indicate (default is -`cleared'). -This function is rather complicated because it must preserve both -the overall formatting of the ledger entry, as well as ensuring -that the most minimal display format is used. This could be -achieved more certainly by passing the entry to ledger for -formatting, but doing so causes inline math expressions to be -dropped." - (interactive) - (let ((bounds (ledger-current-entry-bounds)) - clear cleared) - ;; Uncompact the entry, to make it easier to toggle the - ;; transaction - (save-excursion - (goto-char (car bounds)) - (skip-chars-forward "0-9./= \t") - (setq cleared (and (member (char-after) '(?\* ?\!)) - (char-after))) - (when cleared - (let ((here (point))) - (skip-chars-forward "*! ") - (let ((width (- (point) here))) - (when (> width 0) - (delete-region here (point)) - (if (search-forward " " (line-end-position) t) - (insert (make-string width ? )))))) - (forward-line) - (while (looking-at "[ \t]") - (skip-chars-forward " \t") - (insert cleared " ") - (if (search-forward " " (line-end-position) t) - (delete-char 2)) - (forward-line)))) - ;; Toggle the individual transaction - (save-excursion - (goto-char (line-beginning-position)) - (when (looking-at "[ \t]") - (skip-chars-forward " \t") - (let ((here (point)) - (cleared (member (char-after) '(?\* ?\!)))) - (skip-chars-forward "*! ") - (let ((width (- (point) here))) - (when (> width 0) - (delete-region here (point)) - (save-excursion - (if (search-forward " " (line-end-position) t) - (insert (make-string width ? )))))) - (let (inserted) - (if cleared - (if (and style (eq style 'cleared)) - (progn - (insert "* ") - (setq inserted t))) - (if (and style (eq style 'pending)) - (progn - (insert "! ") - (setq inserted t)) - (progn - (insert "* ") - (setq inserted t)))) - (if (and inserted - (search-forward " " (line-end-position) t)) - (delete-char 2)) - (setq clear inserted))))) - ;; Clean up the entry so that it displays minimally - (save-excursion - (goto-char (car bounds)) - (forward-line) - (let ((first t) - (state ? ) - (hetero nil)) - (while (and (not hetero) (looking-at "[ \t]")) - (skip-chars-forward " \t") - (let ((cleared (if (member (char-after) '(?\* ?\!)) - (char-after) - ? ))) - (if first - (setq state cleared - first nil) - (if (/= state cleared) - (setq hetero t)))) - (forward-line)) - (when (and (not hetero) (/= state ? )) - (goto-char (car bounds)) - (forward-line) - (while (looking-at "[ \t]") - (skip-chars-forward " \t") - (let ((here (point))) - (skip-chars-forward "*! ") - (let ((width (- (point) here))) - (when (> width 0) - (delete-region here (point)) - (if (search-forward " " (line-end-position) t) - (insert (make-string width ? )))))) - (forward-line)) - (goto-char (car bounds)) - (skip-chars-forward "0-9./= \t") - (insert state " ") - (if (search-forward " " (line-end-position) t) - (delete-char 2))))) - clear)) - -(defun ledger-toggle-current (&optional style) - (interactive) - (if ledger-clear-whole-entries - (ledger-toggle-current-entry style) - (ledger-toggle-current-transaction style))) - -(defvar ledger-mode-abbrev-table) - -;;;###autoload -(define-derived-mode ledger-mode text-mode "Ledger" - "A mode for editing ledger data files." - (set (make-local-variable 'comment-start) ";") - (set (make-local-variable 'comment-end) "") - (set (make-local-variable 'indent-tabs-mode) nil) - - (if (boundp 'font-lock-defaults) - (set (make-local-variable 'font-lock-defaults) - '(ledger-font-lock-keywords nil t))) - - (set (make-local-variable 'pcomplete-parse-arguments-function) - 'ledger-parse-arguments) - (set (make-local-variable 'pcomplete-command-completion-function) - 'ledger-complete-at-point) - (set (make-local-variable 'pcomplete-termination-string) "") - - (let ((map (current-local-map))) - (define-key map [(control ?c) (control ?a)] 'ledger-add-entry) - (define-key map [(control ?c) (control ?d)] 'ledger-delete-current-entry) - (define-key map [(control ?c) (control ?y)] 'ledger-set-year) - (define-key map [(control ?c) (control ?m)] 'ledger-set-month) - (define-key map [(control ?c) (control ?c)] 'ledger-toggle-current) - (define-key map [(control ?c) (control ?r)] 'ledger-reconcile) - (define-key map [(control ?c) (control ?s)] 'ledger-sort) - (define-key map [tab] 'pcomplete) - (define-key map [(control ?i)] 'pcomplete) - (define-key map [(control ?c) tab] 'ledger-fully-complete-entry) - (define-key map [(control ?c) (control ?i)] 'ledger-fully-complete-entry) - (define-key map [(control ?c) (control ?o) (control ?r)] 'ledger-report) - (define-key map [(control ?c) (control ?o) (control ?g)] - 'ledger-report-goto) - (define-key map [(control ?c) (control ?o) (control ?a)] - 'ledger-report-redo) - (define-key map [(control ?c) (control ?o) (control ?s)] - 'ledger-report-save) - (define-key map [(control ?c) (control ?o) (control ?e)] - 'ledger-report-edit) - (define-key map [(control ?c) (control ?o) (control ?k)] - 'ledger-report-kill))) - -;; Reconcile mode - -(defvar ledger-buf nil) -(defvar ledger-acct nil) - -(defun ledger-display-balance () - (let ((buffer ledger-buf) - (account ledger-acct)) - (with-temp-buffer - (let ((exit-code (ledger-run-ledger buffer "-C" "balance" account))) - (if (/= 0 exit-code) - (message "Error determining cleared balance") - (goto-char (1- (point-max))) - (goto-char (line-beginning-position)) - (delete-horizontal-space) - (message "Cleared balance = %s" - (buffer-substring-no-properties (point) - (line-end-position)))))))) - -(defun ledger-reconcile-toggle () - (interactive) - (let ((where (get-text-property (point) 'where)) - (account ledger-acct) - (inhibit-read-only t) - cleared) - (when (equal (car where) "") - (with-current-buffer ledger-buf - (goto-char (cdr where)) - (setq cleared (ledger-toggle-current 'pending))) - (if cleared - (add-text-properties (line-beginning-position) - (line-end-position) - (list 'face 'bold)) - (remove-text-properties (line-beginning-position) - (line-end-position) - (list 'face)))) - (forward-line))) - -(defun ledger-auto-reconcile (balance date) - (interactive "sReconcile to balance (negative for a liability): \nsStatement date (default: now): ") - (let ((buffer ledger-buf) - (account ledger-acct) cleared) - ;; attempt to auto-reconcile in the background - (with-temp-buffer - (let ((exit-code - (ledger-run-ledger buffer "--format" "%xB\\n" - "--reconcile" balance "--reconcile-date" date - "register" account))) - (if (/= 0 exit-code) - (error "Failed to reconcile account '%s' to balance '%s'" - account balance) - (goto-char (point-min)) - (unless (looking-at "[0-9]") - (error (buffer-string))) - (while (not (eobp)) - (setq cleared - (cons (1+ (read (current-buffer))) cleared)) - (forward-line))))) - (goto-char (point-min)) - (with-current-buffer ledger-buf - (setq cleared (mapcar 'copy-marker (nreverse cleared)))) - (let ((inhibit-redisplay t)) - (dolist (pos cleared) - (while (and (not (eobp)) - (/= pos (cdr (get-text-property (point) 'where)))) - (forward-line)) - (unless (eobp) - (ledger-reconcile-toggle)))) - (goto-char (point-min)))) - -(defun ledger-reconcile-refresh () - (interactive) - (let ((inhibit-read-only t) - (line (count-lines (point-min) (point)))) - (erase-buffer) - (ledger-do-reconcile) - (set-buffer-modified-p t) - (goto-char (point-min)) - (forward-line line))) - -(defun ledger-reconcile-refresh-after-save () - (let ((buf (get-buffer "*Reconcile*"))) - (if buf - (with-current-buffer buf - (ledger-reconcile-refresh) - (set-buffer-modified-p nil))))) - -(defun ledger-reconcile-add () - (interactive) - (with-current-buffer ledger-buf - (call-interactively #'ledger-add-entry)) - (ledger-reconcile-refresh)) - -(defun ledger-reconcile-delete () - (interactive) - (let ((where (get-text-property (point) 'where))) - (when (equal (car where) "") - (with-current-buffer ledger-buf - (goto-char (cdr where)) - (ledger-delete-current-entry)) - (let ((inhibit-read-only t)) - (goto-char (line-beginning-position)) - (delete-region (point) (1+ (line-end-position))) - (set-buffer-modified-p t))))) - -(defun ledger-reconcile-visit () - (interactive) - (let ((where (get-text-property (point) 'where))) - (when (equal (car where) "") - (switch-to-buffer-other-window ledger-buf) - (goto-char (cdr where))))) - -(defun ledger-reconcile-save () - (interactive) - (with-current-buffer ledger-buf - (save-buffer)) - (set-buffer-modified-p nil) - (ledger-display-balance)) - -(defun ledger-reconcile-quit () - (interactive) - (kill-buffer (current-buffer))) - -(defun ledger-reconcile-finish () - (interactive) - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (let ((where (get-text-property (point) 'where)) - (face (get-text-property (point) 'face))) - (if (and (eq face 'bold) - (equal (car where) "")) - (with-current-buffer ledger-buf - (goto-char (cdr where)) - (ledger-toggle-current 'cleared)))) - (forward-line 1))) - (ledger-reconcile-save)) - -(defun ledger-do-reconcile () - (let* ((buf ledger-buf) - (account ledger-acct) - (items - (with-temp-buffer - (let ((exit-code - (ledger-run-ledger buf "--uncleared" "emacs" account))) - (when (= 0 exit-code) - (goto-char (point-min)) - (unless (eobp) - (unless (looking-at "(") - (error (buffer-string))) - (read (current-buffer)))))))) - (dolist (item items) - (let ((index 1)) - (dolist (xact (nthcdr 5 item)) - (let ((beg (point)) - (where - (with-current-buffer buf - (cons - (nth 0 item) - (if ledger-clear-whole-entries - (copy-marker (nth 1 item)) - (copy-marker (nth 0 xact))))))) - (insert (format "%s %-30s %-25s %15s\n" - (format-time-string "%m/%d" (nth 2 item)) - (nth 4 item) (nth 1 xact) (nth 2 xact))) - (if (nth 3 xact) - (set-text-properties beg (1- (point)) - (list 'face 'bold - 'where where)) - (set-text-properties beg (1- (point)) - (list 'where where)))) - (setq index (1+ index))))) - (goto-char (point-min)) - (set-buffer-modified-p nil) - (toggle-read-only t))) - -(defun ledger-reconcile (account &optional arg) - (interactive "sAccount to reconcile: \nP") - (let ((buf (current-buffer)) - (rbuf (get-buffer "*Reconcile*"))) - (if rbuf - (kill-buffer rbuf)) - (add-hook 'after-save-hook 'ledger-reconcile-refresh-after-save) - (with-current-buffer - (pop-to-buffer (get-buffer-create "*Reconcile*")) - (ledger-reconcile-mode) - (set (make-local-variable 'ledger-buf) buf) - (set (make-local-variable 'ledger-acct) account) - (ledger-do-reconcile) - (when arg - (sit-for 0 0) - (call-interactively #'ledger-auto-reconcile))))) - -(defvar ledger-reconcile-mode-abbrev-table) - -(define-derived-mode ledger-reconcile-mode text-mode "Reconcile" - "A mode for reconciling ledger entries." - (let ((map (make-sparse-keymap))) - (define-key map [(control ?m)] 'ledger-reconcile-visit) - (define-key map [return] 'ledger-reconcile-visit) - (define-key map [(control ?c) (control ?c)] 'ledger-reconcile-finish) - (define-key map [(control ?c) (control ?r)] 'ledger-auto-reconcile) - (define-key map [(control ?x) (control ?s)] 'ledger-reconcile-save) - (define-key map [(control ?l)] 'ledger-reconcile-refresh) - (define-key map [? ] 'ledger-reconcile-toggle) - (define-key map [?a] 'ledger-reconcile-add) - (define-key map [?d] 'ledger-reconcile-delete) - (define-key map [?n] 'next-line) - (define-key map [?p] 'previous-line) - (define-key map [?r] 'ledger-auto-reconcile) - (define-key map [?s] 'ledger-reconcile-save) - (define-key map [?q] 'ledger-reconcile-quit) - (use-local-map map))) - -;; Context sensitivity - -(defconst ledger-line-config - '((entry - (("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*?\\)[ \t]*;\\(.*\\)[ \t]*$" - (date nil status nil nil code payee comment)) - ("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*\\)[ \t]*$" - (date nil status nil nil code payee)))) - (acct-transaction - (("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" - (indent account commodity amount nil comment)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*$" - (indent account commodity amount nil)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" - (indent account amount nil commodity comment)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*$" - (indent account amount nil commodity)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" - (indent account amount nil commodity comment)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*$" - (indent account amount nil commodity)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" - (indent account comment)) - ("\\(^[ \t]+\\)\\(.*?\\)[ \t]*$" - (indent account)))))) - -(defun ledger-extract-context-info (line-type pos) - "Get context info for current line. - -Assumes point is at beginning of line, and the pos argument specifies -where the \"users\" point was." - (let ((linfo (assoc line-type ledger-line-config)) - found field fields) - (dolist (re-info (nth 1 linfo)) - (let ((re (nth 0 re-info)) - (names (nth 1 re-info))) - (unless found - (when (looking-at re) - (setq found t) - (dotimes (i (length names)) - (when (nth i names) - (setq fields (append fields - (list - (list (nth i names) - (match-string-no-properties (1+ i)) - (match-beginning (1+ i)))))))) - (dolist (f fields) - (and (nth 1 f) - (>= pos (nth 2 f)) - (setq field (nth 0 f)))))))) - (list line-type field fields))) - -(defun ledger-context-at-point () - "Return a list describing the context around point. - -The contents of the list are the line type, the name of the field -point containing point, and for selected line types, the content of -the fields in the line in a association list." - (let ((pos (point))) - (save-excursion - (beginning-of-line) - (let ((first-char (char-after))) - (cond ((equal (point) (line-end-position)) - '(empty-line nil nil)) - ((memq first-char '(?\ ?\t)) - (ledger-extract-context-info 'acct-transaction pos)) - ((memq first-char '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) - (ledger-extract-context-info 'entry pos)) - ((equal first-char ?\=) - '(automated-entry nil nil)) - ((equal first-char ?\~) - '(period-entry nil nil)) - ((equal first-char ?\!) - '(command-directive)) - ((equal first-char ?\;) - '(comment nil nil)) - ((equal first-char ?Y) - '(default-year nil nil)) - ((equal first-char ?P) - '(commodity-price nil nil)) - ((equal first-char ?N) - '(price-ignored-commodity nil nil)) - ((equal first-char ?D) - '(default-commodity nil nil)) - ((equal first-char ?C) - '(commodity-conversion nil nil)) - ((equal first-char ?i) - '(timeclock-i nil nil)) - ((equal first-char ?o) - '(timeclock-o nil nil)) - ((equal first-char ?b) - '(timeclock-b nil nil)) - ((equal first-char ?h) - '(timeclock-h nil nil)) - (t - '(unknown nil nil))))))) - -(defun ledger-context-other-line (offset) - "Return a list describing context of line offset for existing position. - -Offset can be positive or negative. If run out of buffer before reaching -specified line, returns nil." - (save-excursion - (let ((left (forward-line offset))) - (if (not (equal left 0)) - nil - (ledger-context-at-point))))) - -(defun ledger-context-line-type (context-info) - (nth 0 context-info)) - -(defun ledger-context-current-field (context-info) - (nth 1 context-info)) - -(defun ledger-context-field-info (context-info field-name) - (assoc field-name (nth 2 context-info))) - -(defun ledger-context-field-present-p (context-info field-name) - (not (null (ledger-context-field-info context-info field-name)))) - -(defun ledger-context-field-value (context-info field-name) - (nth 1 (ledger-context-field-info context-info field-name))) - -(defun ledger-context-field-position (context-info field-name) - (nth 2 (ledger-context-field-info context-info field-name))) - -(defun ledger-context-field-end-position (context-info field-name) - (+ (ledger-context-field-position context-info field-name) - (length (ledger-context-field-value context-info field-name)))) - -(defun ledger-context-goto-field-start (context-info field-name) - (goto-char (ledger-context-field-position context-info field-name))) - -(defun ledger-context-goto-field-end (context-info field-name) - (goto-char (ledger-context-field-end-position context-info field-name))) - -(defun ledger-entry-payee () - "Returns the payee of the entry containing point or nil." - (let ((i 0)) - (while (eq (ledger-context-line-type (ledger-context-other-line i)) 'acct-transaction) - (setq i (- i 1))) - (let ((context-info (ledger-context-other-line i))) - (if (eq (ledger-context-line-type context-info) 'entry) - (ledger-context-field-value context-info 'payee) - nil)))) - -;; Ledger report mode - -(defvar ledger-report-buffer-name "*Ledger Report*") - -(defvar ledger-report-name nil) -(defvar ledger-report-cmd nil) -(defvar ledger-report-name-prompt-history nil) -(defvar ledger-report-cmd-prompt-history nil) -(defvar ledger-original-window-cfg nil) - -(defvar ledger-report-mode-abbrev-table) - -(define-derived-mode ledger-report-mode text-mode "Ledger-Report" - "A mode for viewing ledger reports." - (let ((map (make-sparse-keymap))) - (define-key map [? ] 'scroll-up) - (define-key map [?r] 'ledger-report-redo) - (define-key map [?s] 'ledger-report-save) - (define-key map [?k] 'ledger-report-kill) - (define-key map [?e] 'ledger-report-edit) - (define-key map [?q] 'ledger-report-quit) - (define-key map [(control ?c) (control ?l) (control ?r)] - 'ledger-report-redo) - (define-key map [(control ?c) (control ?l) (control ?S)] - 'ledger-report-save) - (define-key map [(control ?c) (control ?l) (control ?k)] - 'ledger-report-kill) - (define-key map [(control ?c) (control ?l) (control ?e)] - 'ledger-report-edit) - (use-local-map map))) - -(defun ledger-report-read-name () - "Read the name of a ledger report to use, with completion. - -The empty string and unknown names are allowed." - (completing-read "Report name: " - ledger-reports nil nil nil - 'ledger-report-name-prompt-history nil)) - -(defun ledger-report (report-name edit) - "Run a user-specified report from `ledger-reports'. - -Prompts the user for the name of the report to run. If no name is -entered, the user will be prompted for a command line to run. The -command line specified or associated with the selected report name -is run and the output is made available in another buffer for viewing. -If a prefix argument is given and the user selects a valid report -name, the user is prompted with the corresponding command line for -editing before the command is run. - -The output buffer will be in `ledger-report-mode', which defines -commands for saving a new named report based on the command line -used to generate the buffer, navigating the buffer, etc." - (interactive - (progn - (when (and (buffer-modified-p) - (y-or-n-p "Buffer modified, save it? ")) - (save-buffer)) - (let ((rname (ledger-report-read-name)) - (edit (not (null current-prefix-arg)))) - (list rname edit)))) - (let ((buf (current-buffer)) - (rbuf (get-buffer ledger-report-buffer-name)) - (wcfg (current-window-configuration))) - (if rbuf - (kill-buffer rbuf)) - (with-current-buffer - (pop-to-buffer (get-buffer-create ledger-report-buffer-name)) - (ledger-report-mode) - (set (make-local-variable 'ledger-buf) buf) - (set (make-local-variable 'ledger-report-name) report-name) - (set (make-local-variable 'ledger-original-window-cfg) wcfg) - (ledger-do-report (ledger-report-cmd report-name edit)) - (shrink-window-if-larger-than-buffer)))) - -(defun string-empty-p (s) - "Check for the empty string." - (string-equal "" s)) - -(defun ledger-report-name-exists (name) - "Check to see if the given report name exists. - -If name exists, returns the object naming the report, otherwise returns nil." - (unless (string-empty-p name) - (car (assoc name ledger-reports)))) - -(defun ledger-reports-add (name cmd) - "Add a new report to `ledger-reports'." - (setq ledger-reports (cons (list name cmd) ledger-reports))) - -(defun ledger-reports-custom-save () - "Save the `ledger-reports' variable using the customize framework." - (customize-save-variable 'ledger-reports ledger-reports)) - -(defun ledger-report-read-command (report-cmd) - "Read the command line to create a report." - (read-from-minibuffer "Report command line: " - (if (null report-cmd) "ledger " report-cmd) - nil nil 'ledger-report-cmd-prompt-history)) - -(defun ledger-report-ledger-file-format-specifier () - "Substitute the full path to master or current ledger file - -The master file name is determined by the ledger-master-file buffer-local -variable which can be set using file variables. If it is set, it is used, -otherwise the current buffer file is used." - (ledger-master-file)) - -(defun ledger-read-string-with-default (prompt default) - (let ((default-prompt (concat prompt - (if default - (concat " (" default "): ") - ": ")))) - (read-string default-prompt nil nil default))) - -(defun ledger-report-payee-format-specifier () - "Substitute a payee name - -The user is prompted to enter a payee and that is substitued. If -point is in an entry, the payee for that entry is used as the -default." - ;; It is intended copmletion should be available on existing - ;; payees, but the list of possible completions needs to be - ;; developed to allow this. - (ledger-read-string-with-default "Payee" (regexp-quote (ledger-entry-payee)))) - -(defun ledger-report-account-format-specifier () - "Substitute an account name - -The user is prompted to enter an account name, which can be any -regular expression identifying an account. If point is on an account -transaction line for an entry, the full account name on that line is -the default." - ;; It is intended completion should be available on existing account - ;; names, but it remains to be implemented. - (let* ((context (ledger-context-at-point)) - (default - (if (eq (ledger-context-line-type context) 'acct-transaction) - (regexp-quote (ledger-context-field-value context 'account)) - nil))) - (ledger-read-string-with-default "Account" default))) - -(defun ledger-report-expand-format-specifiers (report-cmd) - (let ((expanded-cmd report-cmd)) - (while (string-match "%(\\([^)]*\\))" expanded-cmd) - (let* ((specifier (match-string 1 expanded-cmd)) - (f (cdr (assoc specifier ledger-report-format-specifiers)))) - (if f - (setq expanded-cmd (replace-match - (save-match-data - (with-current-buffer ledger-buf - (shell-quote-argument (funcall f)))) - t t expanded-cmd)) - (progn - (set-window-configuration ledger-original-window-cfg) - (error "Invalid ledger report format specifier '%s'" specifier))))) - expanded-cmd)) - -(defun ledger-report-cmd (report-name edit) - "Get the command line to run the report." - (let ((report-cmd (car (cdr (assoc report-name ledger-reports))))) - ;; logic for substitution goes here - (when (or (null report-cmd) edit) - (setq report-cmd (ledger-report-read-command report-cmd))) - (setq report-cmd (ledger-report-expand-format-specifiers report-cmd)) - (set (make-local-variable 'ledger-report-cmd) report-cmd) - (or (string-empty-p report-name) - (ledger-report-name-exists report-name) - (ledger-reports-add report-name report-cmd) - (ledger-reports-custom-save)) - report-cmd)) - -(defun ledger-do-report (cmd) - "Run a report command line." - (goto-char (point-min)) - (insert (format "Report: %s\n" cmd) - (make-string (- (window-width) 1) ?=) - "\n") - (shell-command cmd t nil)) - -(defun ledger-report-goto () - "Goto the ledger report buffer." - (interactive) - (let ((rbuf (get-buffer ledger-report-buffer-name))) - (if (not rbuf) - (error "There is no ledger report buffer")) - (pop-to-buffer rbuf) - (shrink-window-if-larger-than-buffer))) - -(defun ledger-report-redo () - "Redo the report in the current ledger report buffer." - (interactive) - (ledger-report-goto) - (erase-buffer) - (ledger-do-report ledger-report-cmd)) - -(defun ledger-report-quit () - "Quit the ledger report buffer." - (interactive) - (ledger-report-goto) - (set-window-configuration ledger-original-window-cfg)) - -(defun ledger-report-kill () - "Kill the ledger report buffer." - (interactive) - (ledger-report-quit) - (kill-buffer (get-buffer ledger-report-buffer-name))) - -(defun ledger-report-edit () - "Edit the defined ledger reports." - (interactive) - (customize-variable 'ledger-reports)) - -(defun ledger-report-read-new-name () - "Read the name for a new report from the minibuffer." - (let ((name "")) - (while (string-empty-p name) - (setq name (read-from-minibuffer "Report name: " nil nil nil - 'ledger-report-name-prompt-history))) - name)) - -(defun ledger-report-save () - "Save the current report command line as a named report." - (interactive) - (ledger-report-goto) - (let (existing-name) - (when (string-empty-p ledger-report-name) - (setq ledger-report-name (ledger-report-read-new-name))) - - (while (setq existing-name (ledger-report-name-exists ledger-report-name)) - (cond ((y-or-n-p (format "Overwrite existing report named '%s' " - ledger-report-name)) - (when (string-equal - ledger-report-cmd - (car (cdr (assq existing-name ledger-reports)))) - (error "Current command is identical to existing saved one")) - (setq ledger-reports - (assq-delete-all existing-name ledger-reports))) - (t - (setq ledger-report-name (ledger-report-read-new-name))))) - - (ledger-reports-add ledger-report-name ledger-report-cmd) - (ledger-reports-custom-save))) - -;; In-place completion support - -(defun ledger-thing-at-point () - (let ((here (point))) - (goto-char (line-beginning-position)) - (cond ((looking-at "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.+?)\\)?\\s-+") - (goto-char (match-end 0)) - 'entry) - ((looking-at "^\\s-+\\([*!]\\s-+\\)?[[(]?\\(.\\)") - (goto-char (match-beginning 2)) - 'transaction) - (t - (ignore (goto-char here)))))) - -(defun ledger-parse-arguments () - "Parse whitespace separated arguments in the current region." - (let* ((info (save-excursion - (cons (ledger-thing-at-point) (point)))) - (begin (cdr info)) - (end (point)) - begins args) - (save-excursion - (goto-char begin) - (when (< (point) end) - (skip-chars-forward " \t\n") - (setq begins (cons (point) begins)) - (setq args (cons (buffer-substring-no-properties - (car begins) end) - args))) - (cons (reverse args) (reverse begins))))) - -(defun ledger-entries () - (let ((origin (point)) - entries-list) - (save-excursion - (goto-char (point-min)) - (while (re-search-forward - (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" - "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) - (unless (and (>= origin (match-beginning 0)) - (< origin (match-end 0))) - (setq entries-list (cons (match-string-no-properties 3) - entries-list))))) - (pcomplete-uniqify-list (nreverse entries-list)))) - -(defvar ledger-account-tree nil) - -(defun ledger-find-accounts () - (let ((origin (point)) account-path elements) - (save-excursion - (setq ledger-account-tree (list t)) - (goto-char (point-min)) - (while (re-search-forward - "^[ \t]+\\([*!]\\s-+\\)?[[(]?\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)" nil t) - (unless (and (>= origin (match-beginning 0)) - (< origin (match-end 0))) - (setq account-path (match-string-no-properties 2)) - (setq elements (split-string account-path ":")) - (let ((root ledger-account-tree)) - (while elements - (let ((entry (assoc (car elements) root))) - (if entry - (setq root (cdr entry)) - (setq entry (cons (car elements) (list t))) - (nconc root (list entry)) - (setq root (cdr entry)))) - (setq elements (cdr elements))))))))) - -(defun ledger-accounts () - (ledger-find-accounts) - (let* ((current (caar (ledger-parse-arguments))) - (elements (and current (split-string current ":"))) - (root ledger-account-tree) - (prefix nil)) - (while (cdr elements) - (let ((entry (assoc (car elements) root))) - (if entry - (setq prefix (concat prefix (and prefix ":") - (car elements)) - root (cdr entry)) - (setq root nil elements nil))) - (setq elements (cdr elements))) - (and root - (sort - (mapcar (function - (lambda (x) - (let ((term (if prefix - (concat prefix ":" (car x)) - (car x)))) - (if (> (length (cdr x)) 1) - (concat term ":") - term)))) - (cdr root)) - 'string-lessp)))) - -(defun ledger-complete-at-point () - "Do appropriate completion for the thing at point" - (interactive) - (while (pcomplete-here - (if (eq (save-excursion - (ledger-thing-at-point)) 'entry) - (ledger-entries) - (ledger-accounts))))) - -(defun ledger-fully-complete-entry () - "Do appropriate completion for the thing at point" - (interactive) - (let ((name (caar (ledger-parse-arguments))) - xacts) - (save-excursion - (when (eq 'entry (ledger-thing-at-point)) - (when (re-search-backward - (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" - (regexp-quote name) "\\(\t\\|\n\\| [ \t]\\)") nil t) - (forward-line) - (while (looking-at "^\\s-+") - (setq xacts (cons (buffer-substring-no-properties - (line-beginning-position) - (line-end-position)) - xacts)) - (forward-line)) - (setq xacts (nreverse xacts))))) - (when xacts - (save-excursion - (insert ?\n) - (while xacts - (insert (car xacts) ?\n) - (setq xacts (cdr xacts)))) - (forward-line) - (goto-char (line-end-position)) - (if (re-search-backward "\\(\t\\| [ \t]\\)" nil t) - (goto-char (match-end 0)))))) - -;; A sample function for $ users - -(defun ledger-align-dollars (&optional column) - (interactive "p") - (if (= column 1) - (setq column 48)) - (while (search-forward "$" nil t) - (backward-char) - (let ((col (current-column)) - (beg (point)) - target-col len) - (skip-chars-forward "-$0-9,.") - (setq len (- (point) beg)) - (setq target-col (- column len)) - (if (< col target-col) - (progn - (goto-char beg) - (insert (make-string (- target-col col) ? ))) - (move-to-column target-col) - (if (looking-back " ") - (delete-char (- col target-col)) - (skip-chars-forward "^ \t") - (delete-horizontal-space) - (insert " "))) - (forward-line)))) - -;; A sample entry sorting function, which works if entry dates are of -;; the form YYYY/mm/dd. - -(defun ledger-sort () - (interactive) - (save-excursion - (goto-char (point-min)) - (sort-subr - nil - (function - (lambda () - (if (re-search-forward - (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" - "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) - (goto-char (match-beginning 0)) - (goto-char (point-max))))) - (function - (lambda () - (forward-paragraph)))))) - -;; General helper functions - -(defvar ledger-delete-after nil) - -(defun ledger-run-ledger (buffer &rest args) - "run ledger with supplied arguments" - (cond - ((null ledger-binary-path) - (error "The variable `ledger-binary-path' has not been set")) - ((not (file-exists-p ledger-binary-path)) - (error "The file `ledger-binary-path' (\"%s\") does not exist" - ledger-binary-path)) - ((not (file-executable-p ledger-binary-path)) - (error "The file `ledger-binary-path' (\"%s\") cannot be executed" - ledger-binary-path)) - (t - (let ((buf (current-buffer))) - (with-current-buffer buffer - (apply #'call-process-region - (append (list (point-min) (point-max) - ledger-binary-path ledger-delete-after - buf nil "-f" "-") - args))))))) - -(defun ledger-run-ledger-and-delete (buffer &rest args) - (let ((ledger-delete-after t)) - (apply #'ledger-run-ledger buffer args))) - -(defun ledger-set-year (newyear) - "Set ledger's idea of the current year to the prefix argument." - (interactive "p") - (if (= newyear 1) - (setq ledger-year (read-string "Year: " (ledger-current-year))) - (setq ledger-year (number-to-string newyear)))) - -(defun ledger-set-month (newmonth) - "Set ledger's idea of the current month to the prefix argument." - (interactive "p") - (if (= newmonth 1) - (setq ledger-month (read-string "Month: " (ledger-current-month))) - (setq ledger-month (format "%02d" newmonth)))) - -(defvar ledger-master-file nil) - -(defun ledger-master-file () - "Return the master file for a ledger file. - -The master file is either the file for the current ledger buffer or the -file specified by the buffer-local variable ledger-master-file. Typically -this variable would be set in a file local variable comment block at the -end of a ledger file which is included in some other file." - (if ledger-master-file - (expand-file-name ledger-master-file) - (buffer-file-name))) - -(provide 'ledger) - -;;; ledger.el ends here diff --git a/ledger.h b/ledger.h deleted file mode 100644 index 7b351a3e..00000000 --- a/ledger.h +++ /dev/null @@ -1,43 +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 -// - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#if 0 -#include -#include -#include -#include -#endif - -#endif // _LEDGER_H diff --git a/ledger.info b/ledger.info deleted file mode 100644 index 93704958..00000000 --- a/ledger.info +++ /dev/null @@ -1,3640 +0,0 @@ -This is /Users/johnw/src/ledger/ledger.info, produced by makeinfo -version 4.7 from /Users/johnw/src/ledger/ledger.texi. - -INFO-DIR-SECTION User Applications - Copyright (c) 2003-2006, 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. - -START-INFO-DIR-ENTRY -* Ledger: (ledger). Command Line Accounting -END-INFO-DIR-ENTRY - - -File: ledger.info, Node: Top, Next: Introduction, Prev: (dir), Up: (dir) - -Overview -******** - -Copyright (c) 2003-2006, 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. - -* Menu: - -* Introduction:: -* Running Ledger:: -* Keeping a ledger:: -* Using XML:: - - -File: ledger.info, Node: Introduction, Next: Running Ledger, Prev: Top, Up: Top - -1 Introduction -************** - -Ledger is an accounting tool with the moxie to exist. It provides no -bells or whistles, and returns the user to the days before user -interfaces were even a twinkling in their father's CRT. - - What it does offer is a double-entry accounting ledger with all the -flexibility and muscle of its modern day cousins, without any of the -fat. Think of it as the Bran Muffin of accounting tools. - - To use it, you need to start keeping a ledger. This is the basis of -all accounting, and if you haven't started yet, now is the time to -learn. The little booklet that comes with your checkbook is a ledger, -so we'll describe double-entry accounting in terms of that. - - A checkbook ledger records debits (subtractions, or withdrawals) and -credits (additions, or deposits) with reference to a single account: -the checking account. Where the money comes from, and where it goes -to, are described in the payee field, where you write the person or -company's name. The ultimate aim of keeping a checkbook ledger is to -know how much money is available to spend. That's really the aim of -all ledgers. - - What computers add is the ability to walk through these transactions, -and tell you things about your spending habits; to let you devise -budgets and get control over your spending; to squirrel away money into -virtual savings account without having to physically move money around; -etc. As you keep your ledger, you are recording information about your -life and habits, and sometimes that information can start telling you -things you aren't aware of. Such is the aim of all good accounting -tools. - - The next step up from a checkbook ledger, is a ledger that keeps -track of all your accounts, not just checking. In such a ledger, you -record not only who gets paid--in the case of a debit--but where the -money came from. In a checkbook ledger, its assumed that all the money -comes from your checking account. But in a general ledger, you write -transaction two-lines: the source account and target account. _There -must always be a debit from at least one account for every credit made -to another account_. This is what is meant by "double-entry" -accounting: the ledger must always balance to zero, with an equal -number of debits and credits. - - For example, let's say you have a checking account and a brokerage -account, and you can write checks from both of them. Rather than keep -two checkbooks, you decide to use one ledger for both. In this general -ledger you need to record a payment to Pacific Bell for your monthly -phone bill. The cost is $23.00, let's say, and you want to pay it from -your checking account. In the general ledger you need to say where the -money came from, in addition to where it's going to. The entry might -look like this: - - 9/29 BAL Pacific Bell $-200.00 $-200.00 - Equity:Opening Balances $200.00 - 9/29 BAL Checking $100.00 $100.00 - Equity:Opening Balances $-100.00 - 9/29 100 Pacific Bell $23.00 $223.00 - Checking $-23.00 $77.00 - - The first line shows a payment to Pacific Bell for $23.00. Because -there is no "balance" in a general ledger--it's always zero--we write -in the total balance of all payments to "Pacific Bell", which now is -$223.00 (previously the balance was $200.00). This is done by looking -at the last entry for "Pacific Bell" in the ledger, adding $23.00 to -that amount, and writing the total in the balance column. And the -money came from "Checking"--a withdrawal of $23.00--which leaves the -ending balance in "Checking" at $77.00. This is a very manual -procedure; but that's where computers come in... - - The transaction must balance to $0: $23 went to Pacific Bell, $23 -came from Checking. There is nothing left over to be accounted for, -since the money has simply moved from one account to another. This is -the basis of double-entry accounting: that money never pops in or out of -existence; it is always a transaction from one account to another. - - Keeping a general ledger is the same as keeping two separate ledgers: -One for Pacific Bell and one for Checking. In that case, each time a -payment is written into one, you write a corresponding withdrawal into -the other. This makes it easier to write in a "running balance", since -you don't have to look back at the last time the account was -referenced--but it also means having a lot of ledger books, if you deal -with multiple accounts. - - Enter the beauty of computerized accounting. The purpose of the -Ledger program is to make general ledger accounting simple, by keeping -track of the balances for you. Your only job is to enter the -transactions. If a transaction does not balance, Ledger displays an -error and indicates the incorrect transaction.(1) - - In summary, there are two aspects of Ledger use: updating the ledger -data file, and using the Ledger tool to view the summarized result of -your entries. - - And just for the sake of example--as a starting point for those who -want to dive in head-first--here are the ledger entries from above, -formatting as the ledger program wishes to see them: - - 2004/09/29 Pacific Bell - Payable:Pacific Bell $-200.00 - Equity:Opening Balances - - 2004/09/29 Checking - Accounts:Checking $100.00 - Equity:Opening Balances - - 2004/09/29 Pacific Bell - Payable:Pacific Bell $23.00 - Accounts:Checking - - The account balances and registers in this file, if saved as -`ledger.dat', could be reported using: - - $ ledger -f ledger.dat balance - $ ledger -f ledger.dat register checking - $ ledger -f ledger.dat register bell - -* Menu: - -* Building the program:: -* Getting help:: - - ---------- Footnotes ---------- - - (1) In some special cases, it automatically balances this entry for -you. - - -File: ledger.info, Node: Building the program, Next: Getting help, Prev: Introduction, Up: Introduction - -1.1 Building the program -======================== - -Ledger is written in ANSI C++, and should compile on any platform. It -depends on the GNU multiprecision integer library (libgmp), and the -Perl regular expression library (libpcre). It was developed using GNU -make and gcc 3.3, on a PowerBook running OS/X. - - To build and install once you have these libraries on your system, -enter these commands: - - ./configure && make install - - -File: ledger.info, Node: Getting help, Prev: Building the program, Up: Introduction - -1.2 Getting help -================ - -If you need help on how to use Ledger, or run into problems, you can -just the Ledger mailing list at the following Web address: - - https://lists.sourceforge.net/lists/listinfo/ledger-discuss - - You can also find help at the `#ledger' channel on the IRC server -`irc.freenode.net'. - - -File: ledger.info, Node: Running Ledger, Next: Keeping a ledger, Prev: Introduction, Up: Top - -2 Running Ledger -**************** - -Ledger has a very simple command-line interface, named--enticing -enough--`ledger'. It supports a few reporting commands, and a large -number of options for refining the output from those commands. The -basic syntax of any ledger command is: - - ledger [OPTIONS...] COMMAND [ARGS...] - - Command options must always precede the command word. After the -command word there may appear any number of arguments. For most -commands, these arguments are regular expressions that cause the output -to relate only to transactions matching those regular expressions. For -the `entry' command, the arguments have a special meaning, described -below. - - The regular expressions arguments always match the account name that -a transaction refers to. To match on the payee of the entry instead, -precede the regular expression with `--'. For example, the following -balance command reports account totals for rent, food and movies, but -only those whose payee matches Freddie: - - ledger bal rent food movies -- freddie - - There are many, many command options available with the `ledger' -command, and it takes a while to master them. However, none of them -are required to use the basic reporting commands. - -* Menu: - -* Usage overview:: -* Commands:: -* Options:: -* Format strings:: -* Value expressions:: -* Period expressions:: -* File format:: -* Some typical queries:: -* Budgeting and forecasting:: - - -File: ledger.info, Node: Usage overview, Next: Commands, Prev: Running Ledger, Up: Running Ledger - -2.1 Usage overview -================== - -Before getting into the details of how to run Ledger, it will be easier -to introduce the features in the context of their typical usage. To -that end, this section presents a series of recipes, gradually -introducing all of the command-line features of Ledger. - - For the purpose of these examples, assume the environment variable -LEDGER is set to the file `sample.dat' (which is included in the -distribution), and that the contents of that file are: - - = /^Expenses:Books/ - (Liabilities:Taxes) -0.10 - - ~ Monthly - Assets:Bank:Checking $500.00 - Income:Salary - - 2004/05/01 * Checking balance - Assets:Bank:Checking $1,000.00 - Equity:Opening Balances - - 2004/05/01 * Investment balance - Assets:Brokerage 50 AAPL $30.00 - Equity:Opening Balances - - 2004/05/14 * Pay day - Assets:Bank:Checking $500.00 - Income:Salary - - 2004/05/27 Book Store - Expenses:Books $20.00 - Liabilities:MasterCard - - 2004/05/27 (100) Credit card company - Liabilities:MasterCard $20.00 - Assets:Bank:Checking - - This sample file demonstrates a basic principle of accounting which -it is recommended you follow: Keep all of your accounts under five -parent Assets, Liabilities, Income, Expenses and Equity. It is -important to do so in order to make sense out of the following examples. - -2.1.1 Checking balances ------------------------ - -Ledger has seven basic commands, but by far the most often used are -`balance' and `register'. To see a summary balance of all accounts, -use: - - ledger bal - - `bal' is a short-hand for `balance'. This command prints out the -summary totals of the five parent accounts used in `sample.dat': - - $1,480.00 - 50 AAPL Assets - $-2,500.00 Equity - $20.00 Expenses - $-500.00 Income - $-2.00 Liabilities - -------------------- - $-1,502.00 - 50 AAPL - - None of the child accounts are shown, just the parent account totals. -We can see that in `Assets' there is $1,480.00, and 50 shares of Apple -stock. There is also a negative grand total. Usually the grand total -is zero, which means that all accounts balance(1). In this case, since -the 50 shares of Apple stock cost $1,500.00 dollars, then these two -amounts balance each other in the grand total. The extra $2.00 comes -from a virtual transaction being added by the automatic entry at the -top of the file. The entry is virtual because the account name was -surrounded by parentheses in an automatic entry. Automatic entries -will be discussed later, but first let's remove the virtual transaction -from the balance report by using the `--real' option: - - ledger --real bal - - Now the report is: - - $1,480.00 - 50 AAPL Assets - $-2,500.00 Equity - $20.00 Expenses - $-500.00 Income - -------------------- - $-1,500.00 - 50 AAPL - - Since the liability was a virtual transaction, it has dropped from -the report and we see that final total is balanced. - - But we only know that it balances because `sample.dat' is quite -simple, and we happen to know that the 50 shares of Apple stock cost -$1,500.00. We can verify that things really balance by reporting the -Apple shares in terms of their cost, instead of their quantity. To do -this requires the `--basis', or `-B', option: - - ledger --real -B bal - - This command reports: - - $2,980.00 Assets - $-2,500.00 Equity - $20.00 Expenses - $-500.00 Income - - With the basis cost option, the grand total has disappeared, as it is -now zero. The confirms that the cost of everything balances to zero, -_which must always be true_. Reporting the real basis cost should -never yield a remainder(2). - -2.1.1.1 Sub-account balances -............................ - -The totals reported by the balance command are only the topmost parent -accounts. To see the totals of all child accounts as well, use the -`-s' option: - - ledger --real -B -s bal - - This reports: - - $2,980.00 Assets - $1,480.00 Bank:Checking - $1,500.00 Brokerage - $-2,500.00 Equity:Opening Balances - $20.00 Expenses:Books - $-500.00 Income:Salary - - This shows that the `Assets' total is made up from two child -account, but that the total for each of the other accounts comes from -one child account. - - Sometimes you may have a lot of children, nested very deeply, but -only want to report the first two levels. This can be done with a -display predicate, using a value expression. In the value expression, -`T' represents the reported total, and `l' is the display level for the -account: - - ledger --real -B -d "T&l<=2" bal - - This reports: - - $2,980.00 Assets - $1,480.00 Bank - $1,500.00 Brokerage - $-2,500.00 Equity:Opening Balances - $20.00 Expenses:Books - $-500.00 Income:Salary - - Instead of reporting `Bank:Checking' as a child of `Assets', it -report only `Bank', since that account is a nesting level of 2, while -`Checking' is at level 3. - - To review the display predicate used--`T&l<=2'--this rather terse -expression means: Display an account only if it has a non-zero total -(`T'), and its nesting level is less than or equal to 2 (`l<=2'). - -2.1.1.2 Specific account balances -................................. - -While reporting the totals for all accounts can be useful, most often -you will want to check the balance of a specific account or accounts. -To do this, put one or more account names after the balance command. -Since these names are really regular expressions, you can use partial -names if you wish: - - ledger bal checking - - Reports: - - $1,480.00 Assets:Bank:Checking - - Any number of names may be used: - - ledger bal checking broker liab - - Reports: - - $1,480.00 Assets:Bank:Checking - 50 AAPL Assets:Brokerage - $-2.00 Liabilities - - In this case no grand total is reported, because you are asking for -specific account balances. - - For those comfortable with regular expressions, any Perl regexp is -allowed: - - ledger bal ^assets.*checking ^liab - - Reports: - - $1,480.00 Assets:Bank:Checking - $-2.00 Liabilities:Taxes - -2.1.2 The register report -------------------------- - -While the `balance' command can be very handy for checking account -totals, by far the most powerful of Ledger's reporting tools is the -`register' command. In fact, internally both commands use the same -logic, but report the results differently: `balance' shows the summary -totals, while `register' reports each transaction and how it -contributes to that total. - - Paradoxically, the most basic form of `register' is almost never -used, since it displays every transaction: - - ledger reg - - `reg' is a short-hand for `register'. This command reports: - - 2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 - Equity:Opening Balan.. $-1,000.00 0 - 2004/05/01 Investment balance Assets:Brokerage 50 AAPL 50 AAPL - Equity:Opening Balan.. $-1,500.00 $-1,500.00 - 50 AAPL - 2004/05/14 Pay day Assets:Bank:Checking $500.00 $-1,000.00 - 50 AAPL - Income:Salary $-500.00 $-1,500.00 - 50 AAPL - 2004/05/27 Book Store Expenses:Books $20.00 $-1,480.00 - 50 AAPL - Liabilities:MasterCard $-20.00 $-1,500.00 - 50 AAPL - (Liabilities:Taxes) $-2.00 $-1,502.00 - 50 AAPL - 2004/05/27 Credit card company Liabilities:MasterCard $20.00 $-1,482.00 - 50 AAPL - Assets:Bank:Checking $-20.00 $-1,502.00 - 50 AAPL - - This rather verbose output shows every account transaction in -`sample.dat', and how it affects the running total. The final total is -identical to what we saw with the plain `balance' command. To see how -things really balance, we can use `--real -B', just as we did with -`balance': - - ledger --real -B reg - - Reports: - - 2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 - Equity:Opening Balan.. $-1,000.00 0 - 2004/05/01 Investment balance Assets:Brokerage $1,500.00 $1,500.00 - Equity:Opening Balan.. $-1,500.00 0 - 2004/05/14 Pay day Assets:Bank:Checking $500.00 $500.00 - Income:Salary $-500.00 0 - 2004/05/27 Book Store Expenses:Books $20.00 $20.00 - Liabilities:MasterCard $-20.00 0 - 2004/05/27 Credit card company Liabilities:MasterCard $20.00 $20.00 - Assets:Bank:Checking $-20.00 0 - - Here we see that everything balances to zero in the end, as it must. - -2.1.2.1 Specific register queries -................................. - -The most common use of the register command is to summarize -transactions based on the account(s) they affect. Using `sample.dat' -as as example, we could look at all book purchases using: - - ledger reg books - - Reports: - - 2004/05/29 Book Store Expenses:Books $20.00 $20.00 - - If a double-dash (`--') occurs in the list of regular expressions, -any following arguments are matched against payee names, instead of -account names: - - ledger reg ^liab -- credit - - Reports: - - 2004/05/29 Credit card company Liabilities:MasterCard $20.00 $20.00 - - There are many reporting options for tailoring which transactions are -found, and also how to summarize the various amounts and totals that -result. These are plumbed in greater depth below. - -2.1.3 Selecting transactions ----------------------------- - -Although the easiest way to use the register is to report all the -transactions affecting a set of accounts, it can often result in more -information than you want. To cope with an ever-growing amount of -data, there are several options which can help you pinpoint your report -to exactly the transactions that interest you most. This is called the -"calculation" phase of Ledger. All of its related options are -documented under `--help-calc'. - -2.1.3.1 By date -............... - -`--current'(`-c') displays entries occurring on or before the current -date. Any entry recorded for a future date will be ignored, as if it -had not been seen. This is useful if you happen to pre-record entries, -but still wish to view your balances in terms of what is available -today. - - `--begin DATE' (`-b DATE') limits the report to only those entries -occurring on or after DATE. The running total in the register will -start at zero with the first transaction, even if there are earlier -entries. - - To limit the display only, but still add earlier transactions to the -running total, use the display expression `-d 'd>=[DATE]''): - - ledger --basis -b may -d 'd>=[5/14]' reg ^assets - - Reports: - - 2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 - 2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 - - In this example, the displayed transactions start from `5/14', but -the calculated total starts from the beginning of `may'. - - `--end DATE' (`-e DATE') states when reporting should end, both -calculation and display. The ending date is inclusive. - - The DATE argument to the `-b' and `-e' options can be rather -flexible. Assuming the current date to be November 15, 2004, then all -of the following are equivalent: - - ledger -b oct bal - ledger -b "this oct" bal - ledger -b 2004/10 bal - ledger -b 10 bal - ledger -b last bal - ledger -b "last month" bal - - To constrain the report to a specific time period, use `--period' -(`-p'). A time period may have both a beginning and an end, or -neither, as well as a specified interval. Here are a few examples: - - ledger -p 2004 bal - ledger -p august bal - ledger -p "from aug to oct" bal - ledger -p "daily from 8/1 to 8/15" bal - ledger -p "weekly since august" bal - ledger -p "monthly from feb to oct" bal - ledger -p "quarterly in 2004" bal - ledger -p yearly bal - - See *Note Period expressions:: for more on syntax. Also, all of the -options `-b', `-e' and `-p' may be used together, but whatever -information occurs last takes priority. An example of such usage (in a -script, perhaps) would be: - - ledger -b 2004 -e 2005 -p monthly reg ^expenses - - This command is identical to: - - ledger -p "monthly in 2004" reg ^expenses - - The transactions within a period may be sorted using -`--period-sort', which takes a value expression. This is similar to -the `--sort' option, except that it sorts within each period entry, -rather than sorting all transactions in the report. See the -documentation on `--sort' below for more details. - -2.1.3.2 By status -................. - -By default, all regular transactions are included in each report. To -limit the report to certain kinds of transactions, use one or more of -the following options: - -`-C, --cleared' - Consider only cleared transactions. - -`-U, --uncleared' - Consider only uncleared and pending transactions. - -`-R, --real' - Consider only real (non-virtual) transactions. - -`-L, --actual' - Consider only actual (non-automated) transactions. - - Cleared transactions are indicated by an asterix placed just before -the payee name in a transaction. The meaning of this flag is up to the -user, but typically it means that an entry has been seen on a financial -statement. Pending transactions use an exclamation mark in the same -position, but are mainly used only by reconciling software. Uncleared -transactions are for things like uncashed checks, credit charges that -haven't appeared on a statement yet, etc. - - Real transactions are all non-virtual transactions, where the account -name is not surrounded by parentheses or square brackets. Virtual -transactions are useful for showing a transfer of money that never -really happened, like money set aside for savings without actually -transferring it from the parent account. - - Actual transactions are those not generated, either as part of an -automated entry, or a budget or forecast report. A useful of when you -might like to filter out generated transactions is with a budget: - - ledger --budget --actual reg ^expenses - - This command outputs all transactions affecting a budgeted account, -but without subtracting the budget amount (because the generated -transactions are suppressed with `--actual'). The report shows how -much you actually spent on budgeted items. - -2.1.3.3 By relationship -....................... - -Normally, a register report includes only the transactions that match -the regular expressions specified after the command word. For example, -to report all expenses: - - ledger reg ^expenses - - This reports: - - 2004/05/29 Book Store Expenses:Books $20.00 $20.00 - - Using `--related' (`-r') reports the transactions that did not match -your query, but only in entries that otherwise would have matched. -This has the effect of indicating where money came from, or when to: - - ledger -r reg ^expenses - - Reports: - - 2004/05/29 Book Store Liabilities:MasterCard $20.00 $20.00 - -2.1.3.4 By budget -................. - -There is more information about budgeting and forecasting in *Note -Budgeting and forecasting::. Basically, if you have any period entries -in your ledger file, you can use these options. A period entry looks -like: - - ~ Monthly - Assets:Bank:Checking $500.00 - Income:Salary - - The difference from a regular entry is that the first line begins -with a tilde (~), and instead of a payee there's a period expression -(*Note Period expressions::). Otherwise, a period entry is in every -other way the same as a regular entry. - - With such an entry in your ledger file, the `--budget' option will -report only transactions that match a budgeted account. Using -`sample.dat' from above: - - ledger --budget reg ^income - - Reports: - - 2004/05/01 Budget entry Income:Salary $500.00 $500.00 - 2004/05/14 Pay day Income:Salary $-500.00 0 - - The final total is zero, indicating that the budget matched exactly -for the reported period. Budgeting is most often helpful with period -reporting; for example, to show monthly budget results use `--budget -p -monthly'. - - The `--add-budget' option reports all matching transactions in -addition to budget transactions; while `--unbudgeted' shows only those -that don't match a budgeted account. To summarize: - -`--budget' - Show transactions matching budgeted accounts. - -`--unbudgeted' - Show transactions matching unbudgeted accounts. - -`--add-budget' - Show both budgeted and unbudgeted transactions together (i.e., add - the generated budget transactions to the regular report). - - A report with the `--forecast' option will add budgeted transactions -while the specified value expression is true. For example: - - ledger --forecast 'd<[2005] reg ^income - - Reports: - - 2004/05/14 Pay day Income:Salary $-500.00 $-500.00 - 2004/12/01 Forecast entry Income:Salary $-500.00 $-1,000.00 - 2005/01/01 Forecast entry Income:Salary $-500.00 $-1,500.00 - - The date this report was made was November 5, 2004; the reason the -first forecast entry is in december is that forecast entries are only -added for the future, and they only stop after the value expression has -matched at least once, which is why the January entry appears. A -forecast report can be very useful for determining when money will run -out in an account, or for projecting future cash flow: - - ledger --forecast 'd<[2008]' -p yearly reg ^inc ^exp - - This reports balances projected income against projected expenses, -showing the resulting total in yearly intervals until 2008. For the -case of `sample.dat', which has no budgeted expenses, the result of the -above command (in November 2004) is: - - 2004/01/01 - 2004/12/31 Income:Salary $-1,000.00 $-1,000.00 - Expenses:Books $20.00 $-980.00 - 2005/01/01 - 2005/12/31 Income:Salary $-6,000.00 $-6,980.00 - 2006/01/01 - 2006/12/31 Income:Salary $-6,000.00 $-12,980.00 - 2007/01/01 - 2007/12/31 Income:Salary $-6,000.00 $-18,980.00 - 2008/01/01 - 2008/01/01 Income:Salary $-500.00 $-19,480.00 - -2.1.3.5 By value expression -........................... - -Value expressions can be quite complex, and are treated more fully in -*Note Value expressions::. They can be used for limiting a report with -`--limit' (`-l'). The following command report income since august, -but expenses since october: - - ledger -l '(/income/&d>=[aug])|(/expenses/&d>=[oct])' reg - - The basic form of this value expression is `(A&B)|(A&B)'. The `A' -in each part matches against an account name with `/name/', while each -`B' part compares the date of the transaction (`d') with a specified -month. The resulting report will contain only transactions which match -the value expression. - - Another use of value expressions is to calculate the amount reported -for each line of a register report, or for computing the subtotal of -each account shown in a balance report. This example divides each -transaction amount by two: - - ledger -t 'a/2' reg ^exp - - The `-t' option doesn't affect the running total, only how the -transaction amount is displayed. To change the running total, use -`-T'. In that case, you will likely want to use the total (`O') -instead of the amount (`a'): - - ledger -T 'O/2' reg ^exp - -2.1.4 Massaging register output -------------------------------- - -Even after filtering down your data to just the transactions you're -interested in, the default reporting method of one transaction per line -is often still too much. To combat this complexity, it is possible to -ask Ledger to report the details to you in many different forms, -summarized in various ways. This is the "display" phase of Ledger, and -is documented under `--help-disp'. - -2.1.4.1 Summarizing -................... - -When multiple transactions relate to a single entry, they are reported -as part of that entry. For example, in the case of `sample.dat': - - ledger reg -- book - - Reports: - - 2004/05/29 Book Store Expenses:Books $20.00 $20.00 - Liabilities:MasterCard $-20.00 0 - (Liabilities:Taxes) $-2.00 $-2.00 - - All three transactions are part of one entry, and as such the entry -details are printed only once. To report every entry on a single line, -use `-n' to collapse entries with multiple transactions: - - ledger -n reg -- book - - Reports: - - 2004/05/29 Book Store $-2.00 $-2.00 - - In the balance report, `-n' causes the grand total not to be -displayed at the bottom of the report. - - If an account occurs more than once in a report, it is possible to -combine them all and report the total per-account, using `-s'. For -example, this command: - - ledger -B reg ^assets - - Reports: - - 2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 - 2004/05/01 Investment balance Assets:Brokerage $1,500.00 $2,500.00 - 2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 - 2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 - - But if the `-s' option is added, the result becomes: - - 2004/05/01 - 2004/05/29 Assets:Bank:Checking $1,480.00 $1,480.00 - Assets:Brokerage $1,500.00 $2,980.00 - - When account subtotaling is used, only one entry is printed, and the -date and name reflect the range of the combined transactions. - - With `-P', transactions relating to the same payee are combined. In -this case, the date of the combined entry is that of the latest -transaction. - - `-x' changes the payee name for each transaction to be the same as -the commodity it uses. This can be especially useful combined with -other options, like `-P'. For example: - - ledger -Px reg ^assets - - Reports: - - 2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 - 2004/05/01 AAPL Assets:Brokerage 50 AAPL $1,480.00 - 50 AAPL - - This reports shows the subtotal for each commodity held, and where it -is located. To see the basis cost, or initial investment, add `-B'. -Applied to the example above: - - 2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 - 2004/05/01 AAPL Assets:Brokerage $1,500.00 $2,980.00 - - The only other options which affect summarized totals is `-E', which -works only in the balance report. In this case, it shows matching -accounts with a zero a balance, which are ordinarily excluded. This -can be useful to see all the accounts involved in a report, even if -some have no total. - -2.1.4.2 Quick periods -..................... - -Although the `-p' option (also `--period') is much more versatile, -there are other options to make the most common period reports easier: - -`-W, --weekly' - Show weekly sub-totals. Same as `-p weekly'. - -`-M, --monthly' - Show monthly sub-totals. Same as `-p monthly'. - -`-Y, --yearly' - Show yearly sub-totals. Same as `-p yearly'. - - There is one kind of period report cannot be done with `-p'. This -is the `--dow', or "days of the week" report, which shows summarized -totals for each day of the week. The following examples shows a "day -of the week" report of income and expenses: - - ledger --dow reg ^inc ^exp - - Reports: - - 2004/05/27 Thursdays Expenses:Books $20.00 $20.00 - 2004/05/14 Fridays Income:Salary $-500.00 $-480.00 - -2.1.4.3 Ordering and width -.......................... - -The transactions displayed in a report are shown in the same order as -they appear in the ledger file. To change the order and sort a report, -use the `--sort' option. `--sort' takes a value expression to -determine the value to sort against, making it possible to sort -according to complex criteria. Here are some simple and useful -examples: - - ledger --sort d reg ^exp # sort by date - ledger --sort t reg ^exp # sort by amount total - ledger --sort -t reg ^exp # reverse sort by amount total - ledger --sort Ut reg ^exp # sort by abs amount total - - For the balance report, you will want to use `T' instead of `t': - - ledger --sort T reg ^exp # sort by amount total - ledger --sort -T reg ^exp # reverse sort by amount total - ledger --sort UT reg ^exp # sort by abs amount total - - The `--sort' options sorts all transactions in a report. If periods -are used (such as `--monthly'), this can get somewhat confusing. In -that case, you'll probably want to sort within periods using -`--period-sort' instead of `--sort'. - - And if the register seems too cramped, and you have a lot of screen -real estate, you can use `-w' to format the report within 132 acolumns, -instead of 80. You are more likely then to see full payee and account -names, as well as properly formatted totals when long-named commodities -are used. - - If you want only the first or last N entries to be printed--which can -be very useful for viewing the last 10 entries in your checking -account, while also showing the cumulative balance from all -entries--use the `--head' and/or `--tail' options. The two options may -be used simultaneously, for example: - - ledger --tail 20 reg checking - - If the output from your command is very long, Ledger can output the -data to a pager utility, such as `more' or `less': - - ledger --pager /usr/bin/less reg checking - -2.1.4.4 Averages and percentages -................................ - -To see the running total changed to a running average, use `-A'. The -final transaction's total will be the overall average of all displayed -transactions. The works in conjunction with period reporting, so that -you can see your monthly average expenses with: - - ledger -AM reg ^expenses:food - ledger -AMn reg ^expenses - - This works in the balance report too: - - ledger -AM bal ^expenses:food - ledger -AMs bal ^expenses - - The `-D' option changes the running average into a deviation from -the running average. This only makes sense in the register report, -however. - - ledger -DM reg ^expenses:food - - In the balance report only, `-%' changes the reported totals into a -percentage of the parent account. This kind of report is confusing if -negative amounts are involved, and doesn't work at all if multiple -commodities occur in an account's history. It has a somewhat limited -usefulness, therefore, but in certain cases it can be handy, such as -reviewing overall expenses: - - ledger -%s -S T bal ^expenses - -2.1.4.5 Reporting total data -............................ - -Normally in the `xml' report, only transaction amounts are printed. To -include the running total under a `' tag, use `--totals'. This -does not affect any other report. - - In the register report only, the output can be changed with `-j' to -show only the date and the amount--without commodities. This only -makes sense if a single commodity appears in the report, but can be -quite useful for scripting, or passing the data to Gnuplot. To show -only the date and running total, use `-J'. - -2.1.4.6 Display by value expression -................................... - -With `-d' you can decide which transactions (or accounts in the balance -report) are displayed, according to a value expression. The computed -total is not affected, only the display. This can be very useful for -shortening a report without changing the running total: - - ledger -d 'd>=[last month]' reg checking - - This command shows the checking account's register, beginning from -last month, but with the running total reflecting the entire history of -the account. - -2.1.4.7 Change report format -............................ - -When dates are printed in any report, the default format is `%Y/%m/%d', -which yields dates of the form `YYYY/mm/dd'. This can be changed with -`-y', whose argument is a `strftime' string--see your system's C -library documentation for the allowable codes. Mostly you will want to -use `%Y', `%m' and `%d', in whatever combination is convenient for your -locale. - - To change the format of the entire reported line, use `-F'. It -supports quite a large number of options, which are all documented in -*Note Format strings::. In addition, each specific kind of report -(except for `xml') can be changed using one of the following options: - -`--balance-format' - `balance' report. Default: - %20T %2_%-a\n - -`--register-format' - `register' report. Default: - %D %-.20P %-.22A %12.66t %12.80T\n%/%32|%-.22A %12.66t %12.80T\n - -`--print-format' - `print' report. Default: - %D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n - -`--plot-amount-format' - `register' report when `-j' (plot amount) is used. Default: - %D %(St)\n - -`--plot-total-format' - `register' report when `-J' (plot total) is used. Default: - %D %(ST)\n - -`--equity-format' - `equity' report. Default: - \n%D %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n - -`--prices-format' - `prices' report. Default: - \n%D %Y%C%P\n%/ %-34W %12t\n - -`--wide-register-format' - `register' report when `-w' (wide) is used. Default: - %D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n - -2.1.5 Standard queries ----------------------- - -If your ledger file uses the standard top-level accounts: Assets, -Liabilities, Income, Expenses, Equity: then the following queries will -enable you to generate some typical accounting reports from your data. - - Your _net worth_ can be determined by balancing assets against -liabilities: - - ledger bal ^assets ^liab - - By removing long-term investment and loan accounts, you can see your -current net liquidity (or liquid net worth): - - ledger bal ^assets ^liab -retirement -brokerage -loan - - Balancing expenses against income yields your _cash flow_, or net -profit/loss: - - ledger bal ^exp ^inc - - In this case, if the number is positive it means you spent more than -you earned during the report period. - - The most often used command is the "balance" command: - - export LEDGER=/home/johnw/doc/ledger.dat - ledger balance - - Here I've set my Ledger environment variable to point to where my -ledger file is hiding. Thereafter, I needn't specify it again. - -2.1.6 Reporting balance totals ------------------------------- - -The balance command prints out the summarized balances of all my -top-level accounts, excluding sub-accounts. In order to see the -balances for a specific account, just specify a regular expression -after the balance command: - - ledger balance expenses:food - - This will show all the money that's been spent on food, since the -beginning of the ledger. For food spending just this month -(September), use: - - ledger -p sep balance expenses:food - - Or maybe you want to see all of your assets, in which case the -s -(show sub-accounts) option comes in handy: - - ledger -s balance ^assets - - To exclude a particular account, use a regular expression with a -leading minus sign. The following will show all expenses, but without -food spending: - - ledger balance expenses -food - -2.1.7 Reporting percentages ---------------------------- - -There is no built-in way to report transaction amounts or account -balances in terms of percentages - - ---------- Footnotes ---------- - - (1) It is impossible for accounts not to balance in ledger; it -reports an error if a transaction does not balance - - (2) If it ever does, then generated transactions are involved, which -can be removed using `--actual' - - -File: ledger.info, Node: Commands, Next: Options, Prev: Usage overview, Up: Running Ledger - -2.2 Commands -============ - -2.2.1 balance -------------- - -The `balance' command reports the current balance of all accounts. It -accepts a list of optional regexps, which confine the balance report to -the matching accounts. If an account contains multiple types of -commodities, each commodity's total is reported separately. - -2.2.2 register --------------- - -The `register' command displays all the transactions occurring in a -single account, line by line. The account regexp must be specified as -the only argument to this command. If any regexps occur after the -required account name, the register will contain only those -transactions that match. Very useful for hunting down a particular -transaction. - - The output from `register' is very close to what a typical -checkbook, or single-account ledger, would look like. It also shows a -running balance. The final running balance of any register should -always be the same as the current balance of that account. - - If you have Gnuplot installed, you may plot the amount or running -total of any register by using the script `report', which is included -in the Ledger distribution. The only requirement is that you add -either `-j' or `-J' to your register command, in order to plot either -the amount or total column, respectively. - -2.2.3 print ------------ - -The `print' command prints out ledger entries in a textual format that -can be parsed by Ledger. They will be properly formatted, and output -in the most economic form possible. The "print" command also takes a -list of optional regexps, which will cause only those transactions -which match in some way to be printed. - - The `print' command can be a handy way to clean up a ledger file -whose formatting has gotten out of hand. - -2.2.4 output ------------- - -The `output' command is very similar to the `print' command, except -that it attempts to replicate the specified ledger file exactly. The -format of the command is: - - ledger -f FILENAME output FILENAME - - Where `FILENAME' is the name of the ledger file to output. The -reason for specifying this command is that only entries contained -within that file will be output, and not an included entries (as can -happen with the `print' command). - -2.2.5 xml ---------- - -The `xml' command outputs results similar to what `print' and -`register' display, but as an XML form. This data can then be read in -and processed. Use the `--totals' option to include the running total -with each transaction. - -2.2.6 emacs ------------ - -The `emacs' command outputs results in a form that can be read directly -by Emacs Lisp. The format of the sexp is: - - ((BEG-POS CLEARED DATE CODE PAYEE - (ACCOUNT AMOUNT)...) ; list of transactions - ...) ; list of entries - -2.2.7 equity ------------- - -The `equity' command prints out accounts balances as if they were -entries. This makes it easy to establish the starting balances for an -account, such as when *Note Archiving previous years::. - -2.2.8 prices ------------- - -The `prices' command displays the price history for matching -commodities. The `-A' flag is useful with this report, to display the -running average price, or `-D' to show each price's deviation from that -average. - - There is also a `pricesdb' command which outputs the same -information as `prices', but does in a format that can be parsed by -Ledger. - -2.2.9 entry ------------ - -The `entry' commands simplifies the creation of new entries. It works -on the principle that 80% of all transactions are variants of earlier -transactions. Here's how it works: - - Say you currently have this transaction in your ledger file: - - 2004/03/15 * Viva Italiano - Expenses:Food $12.45 - Expenses:Tips $2.55 - Liabilities:MasterCard $-15.00 - - Now it's `2004/4/9', and you've just eating at `Viva Italiano' -again. The exact amounts are different, but the overall form is the -same. With the `entry' command you can type: - - ledger entry 2004/4/9 viva food 11 tips 2.50 - - This produces the following output: - - 2004/04/09 Viva Italiano - Expenses:Food $11.00 - Expenses:Tips $2.50 - Liabilities:MasterCard $-13.50 - - It works by finding a past transaction matching the regular -expression `viva', and assuming that any accounts or amounts specified -will be similar to that earlier transaction. If Ledger does not -succeed in generating a new entry, an error is printed and the exit -code is set to `1'. - - There is a shell script in the distribution's `scripts' directory -called `entry', which simplifies the task of adding a new entry to your -ledger. It launches `vi' to confirm that the entry looks appropriate. - - Here are a few more examples of the `entry' command, assuming the -above journal entry: - - ledger entry 4/9 viva 11.50 - ledger entry 4/9 viva 11.50 checking # (from `checking') - ledger entry 4/9 viva food 11.50 tips 8 - ledger entry 4/9 viva food 11.50 tips 8 cash - ledger entry 4/9 viva food $11.50 tips $8 cash - ledger entry 4/9 viva dining "DM 11.50" - - -File: ledger.info, Node: Options, Next: Format strings, Prev: Commands, Up: Running Ledger - -2.3 Options -=========== - -With all of the reports, command-line options are useful to modify the -output generated. These command-line options always occur before the -command word. This is done to distinguish options from exclusive -regular expressions, which also begin with a dash. The basic form for -most commands is: - - ledger [OPTIONS] COMMAND [REGEXPS...] [-- [REGEXPS...]] - - The OPTIONS and REGEXPS expressions are both optional. You could -just use `ledger balance', without any options--which prints a summary -of all accounts. But for more specific reporting, or to change the -appearance of the output, options are needed. - -* Menu: - -* Basic options:: -* Report filtering:: -* Output customization:: -* Commodity reporting:: -* Environment variables:: - - -File: ledger.info, Node: Basic options, Next: Report filtering, Prev: Options, Up: Options - -2.3.1 Basic options -------------------- - -These are the most basic command options. Most likely, the user will -want to set them using *Note Environment variables::, instead of using -actual command-line options: - - `--help' (`-h') prints a summary of all the options, and what they -are used for. This can be a handy way to remember which options do -what. This help screen is also printed if ledger is run without a -command. - - `--version' (`-v') prints the current version of ledger and exits. -This is useful for sending bug reports, to let the author know which -version of ledger you are using. - - `--file FILE' (`-f FILE') reads FILE as a ledger file. This command -may be used multiple times. FILE may also be a list of file names -separated by colons. Typically, the environment variable `LEDGER_FILE' -is set, rather than using this command-line option. - - `--output FILE' (`-o FILE') redirects output from any command to -FILE. By default, all output goes to standard output. - - `--init-file FILE' (`-i FILE') causes FILE to be read by ledger -before any other ledger file. This file may not contain any -transactions, but it may contain option settings. To specify options -in the init file, use the same syntax as the command-line. Here's an -example init file: - - --price-db ~/finance/.pricedb - - ; ~/.ledgerrc ends here - - Option settings on the command-line or in the environment always take -precedence over settings in the init file. - - `--cache FILE' identifies FILE as the default binary cache file. -That is, if the ledger files to be read are specified using the -environment variable `LEDGER_FILE', then whenever a command is finished -a binary copy will be written to the specified cache, to speed up the -loading time of subsequent queries. This filename can also be given -using the environment variable `LEDGER_CACHE', or by putting the option -into your init file. The `--no-cache' option causes Ledger to always -ignore the binary cache. - - `--account NAME' (`-a NAME') specifies the default account which QIF -file transactions are assumed to relate to. - - -File: ledger.info, Node: Report filtering, Next: Output customization, Prev: Basic options, Up: Options - -2.3.2 Report filtering ----------------------- - -These options change which transactions affect the outcome of a report, -in ways other than just using regular expressions: - - `--current'(`-c') displays only entries occurring on or before the -current date. - - `--begin DATE' (`-b DATE') constrains the report to entries on or -after DATE. Only entries after that date will be calculated, which -means that the running total in the balance report will always start at -zero with the first matching entry. (Note: This is different from -using `--display' to constrain what is displayed). - - `--end DATE' (`-e DATE') constrains the report so that entries on or -after DATE are not considered. The ending date is inclusive. - - `--period STR' (`-p STR') sets the reporting period to STR. This -will subtotal all matching entries within each period separately, -making it easy to see weekly, monthly, quarterly, etc., transaction -totals. A period string can even specify the beginning and end of the -report range, using simple terms like "last june" or "next month". For -more using period expressions, see *Note Period expressions::. - - `--period-sort EXPR' sorts the transactions within each reporting -period using the value expression EXPR. This is most often useful when -reporting monthly expenses, in order to view the highest expense -categories at the top of each month: - - ledger -M --period-sort -At reg ^Expenses - - `--cleared' (`-C') displays only transactions whose entry has been -marked "cleared" (by placing an asterix to the right of the date). - - `--uncleared' (`-U') displays only transactions whose entry has not -been marked "cleared" (i.e., if there is no asterix to the right of the -date). - - `--real' (`-R') displays only real transactions, not virtual. (A -virtual transaction is indicated by surrounding the account name with -parentheses or brackets; see the section on using virtual transactions -for more information). - - `--actual' (`-L') displays only actual transactions, and not those -created due to automated transactions. - - `--related' (`-r') displays transactions that are related to -whichever transactions would otherwise have matched the filtering -criteria. In the register report, this shows where money went to, or -the account it came from. In the balance report, it shows all the -accounts affected by entries having a related transaction. For -example, if a file had this entry: - - 2004/03/20 Safeway - Expenses:Food $65.00 - Expenses:Cash $20.00 - Assets:Checking $-85.00 - - And the register command was: - - ledger -r register food - - The following would be output, showing the transactions related to -the transaction that matched: - - 2004/03/20 Safeway Expenses:Cash $-20.00 $-20.00 - Assets:Checking $85.00 $65.00 - - `--budget' is useful for displaying how close your transactions meet -your budget. `--add-budget' also shows unbudgeted transactions, while -`--unbudgeted' shows only those. `--forecast' is a related option that -projects your budget into the future, showing how it will affect future -balances. *Note Budgeting and forecasting::. - - `--limit EXPR' (`-l EXPR') limits which transactions take part in -the calculations of a report. - - `--amount EXPR' (`-t EXPR') changes the value expression used to -calculate the "value" column in the `register' report, the amount used -to calculate account totals in the `balance' report, and the values -printed in the `equity' report. *Note Value expressions::. - - `--total EXPR' (`-T EXPR') sets the value expression used for the -"totals" column in the `register' and `balance' reports. - - -File: ledger.info, Node: Output customization, Next: Commodity reporting, Prev: Report filtering, Up: Options - -2.3.3 Output customization --------------------------- - -These options affect only the output, but not which transactions are -used to create it: - - `--collapse' (`-n') causes entries in a `register' report with -multiple transactions to be collapsed into a single, subtotaled entry. - - `--subtotal' (`-s') causes all entries in a `register' report to be -collapsed into a single, subtotaled entry. - - `--by-payee' (`-P') reports subtotals by payee. - - `--comm-as-payee' (`-x') changes the payee of every transaction to -be the commodity used in that transaction. This can be useful when -combined with other options, such as `-s'. - - `--empty' (`-E') includes even empty accounts in the `balance' -report. - - `--weekly' (`-W') reports transaction totals by the week. The week -begins on whichever day of the week begins the month containing that -transaction. To set a specific begin date, use a period string, such -as `weekly from DATE'. `--monthly' (`-M') reports transaction totals -by month; `--yearly' (`-Y') reports transaction totals by year. For -more complex period, using the `--period' option described above. - - `--dow' reports transactions totals for each day of the week. This -is an easy way to see if weekend spending is more than on weekdays. - - `--sort EXPR' (`-S EXPR') sorts a report by comparing the values -determined using the value expression EXPR. For example, using `-S --UT' in the balance report will sort account balances from greatest to -least, using the absolute value of the total. For more on how to use -value expressions, see *Note Value expressions::. - - `--wide' (`-w') causes the default `register' report to assume 132 -columns instead of 80. - - `--head' causes only the first N entries to be printed. This is -different from using the command-line utility `head', which would limit -to the first N transactions. `--tail' outputs only the last N entries. -Both options may be used simultaneously. If a negative amount is -given, it will invert the meaning of the flag (instead of the first -five entries being printed, for example, it would print all but the -first five). - - `--pager' tells Ledger to pass its output to the given pager -program--very useful when the output is especially long. This behavior -can be made the default by setting the `LEDGER_PAGER' environment -variable. - - `--average' (`-A') reports the average transaction value. - - `--deviation' (`-D') reports each transaction's deviation from the -average. It is only meaningful in the `register' and `prices' reports. - - `--percentage' (`-%') shows account subtotals in the `balance' -report as percentages of the parent account. - - `--totals' include running total information in the `xml' report. - - `--amount-data' (`-j') changes the `register' report so that it -output nothing but the date and the value column, and the latter -without commodities. This is only meaningful if the report uses a -single commodity. This data can then be fed to other programs, which -could plot the date, analyze it, etc. - - `--total-data' (`-J') changes the `register' report so that it -output nothing but the date and totals column, without commodities. - - `--display EXPR' (`-d EXPR') limits which transactions or accounts -or actually displayed in a report. They might still be calculated, and -be part of the running total of a register report, for example, but -they will not be displayed. This is useful for seeing last month's -checking transactions, against a running balance which includes all -transaction values: - - ledger -d "d>=[last month]" reg checking - - The output from this command is very different from the following, -whose running total includes only transactions from the last month -onward: - - ledger -p "last month" reg checking - - Which is more useful depends on what you're looking to know: the -total amount for the reporting range (`-p'), or simply a display -restricted to the reporting range (using `-d'). - - `--date-format STR' (`-y STR') changes the basic date format used by -reports. The default uses a date like 2004/08/01, which represents the -default date format of `%Y/%m/%d'. To change the way dates are printed -in general, the easiest way is to put `--date-format FORMAT' in the -Ledger initialization file `~/.ledgerrc' (or the file referred to by -`LEDGER_INIT'). - - `--format STR' (`-F STR') sets the reporting format for whatever -report ledger is about to make. *Note Format strings::. There are -also specific format commands for each report type: - - * `--balance-format STR' - - * `--register-format STR' - - * `--print-format STR' - - * `--plot-amount-format STR' (-j `register') - - * `--plot-total-format STR' (-J `register') - - * `--equity-format STR' - - * `--prices-format STR' - - * `--wide-register-format STR' (-w `register') - - -File: ledger.info, Node: Commodity reporting, Next: Environment variables, Prev: Output customization, Up: Options - -2.3.4 Commodity reporting -------------------------- - -These options affect how commodity values are displayed: - - `--price-db FILE' sets the file that is used for recording -downloaded commodity prices. It is always read on startup, to -determine historical prices. Other settings can be placed in this file -manually, to prevent downloading quotes for a specific, for example. -This is done by adding a line like the following: - - ; Don't download quotes for the dollar, or timelog values - N $ - N h - - `--price-exp MINS' (`-L MINS') sets the expected freshness of price -quotes, in minutes. That is, if the last known quote for any commodity -is older than this value--and if `--download' is being used--then the -Internet will be consulted again for a newer price. Otherwise, the old -price is still considered to be fresh enough. - - `--download' (`-Q') causes quotes to be automagically downloaded, as -needed, by running a script named `getquote' and expecting that script -to return a value understood by ledger. A sample implementation of a -`getquote' script, implemented in Perl, is provided in the -distribution. Downloaded quote price are then appended to the price -database, usually specified using the environment variable -`LEDGER_PRICE_DB'. - - There are several different ways that ledger can report the totals it -displays. The most flexible way to adjust them is by using value -expressions, and the `-t' and `-T' options. However, there are also -several "default" reports, which will satisfy most users basic -reporting needs: - -`-O, --quantity' - Reports commodity totals (this is the default) - -`-B, --basis' - Reports the cost basis for all transactions. - -`-V, --market' - Reports the last known market value for all commodities. - -`-g, --performance' - Reports the net gain/loss for each transaction in a `register' - report. - -`-G --gain' - Reports the net gain/loss for all commodities in the report that - have a price history. - - -File: ledger.info, Node: Environment variables, Prev: Commodity reporting, Up: Options - -2.3.5 Environment variables ---------------------------- - -Every option to ledger may be set using an environment variable. If an -option has a long name such `--this-option', setting the environment -variable `LEDGER_THIS_OPTION' will have the same affect as specifying -that option on the command-line. Options on the command-line always -take precedence over environment variable settings, however. - - Note that you may also permanently specify option values by placing -option settings in the file `~/.ledgerrc', for example: - - --cache /tmp/.mycache - - -File: ledger.info, Node: Format strings, Next: Value expressions, Prev: Options, Up: Running Ledger - -2.4 Format strings -================== - -Format strings may be used to change the output format of reports. -They are specified by passing a formatting string to the `--format' -(`-F') option. Within that string, constructs are allowed which make -it possible to display the various parts of an account or transaction -in custom ways. - - Within a format strings, a substitution is specified using a percent -character (`%'). The basic format of all substitutions is: - - %[-][MIN WIDTH][.MAX WIDTH]EXPR - - If the optional minus sign (`-') follows the percent character, -whatever is substituted will be left justified. The default is right -justified. If a minimum width is given next, the substituted text will -be at least that wide, perhaps wider. If a period and a maximum width -is given, the substituted text will never be wider than this, and will -be truncated to fit. Here are some examples: - - %-P An entry's payee, left justified - %20P The same, right justified, at least 20 chars wide - %.20P The same, no more than 20 chars wide - %-.20P Left justified, maximum twenty chars wide - - The expression following the format constraints can be a single -letter, or an expression enclosed in parentheses or brackets. The -allowable expressions are: - -`%' - Inserts a percent sign. - -`t' - Inserts the results of the value expression specified by `-t'. If - `-t' was not specified, the current report style's value - expression is used. - -`T' - Inserts the results of the value expression specified by `-T'. If - `-T' was not specified, the current report style's value - expression is used. - -`|' - Inserts a single space. This is useful if a width is specified, - for inserting a certain number of spaces. - -`_' - Inserts a space for each level of an account's depth. That is, if - an account has two parents, this construct will insert two spaces. - If a minimum width is specified, that much space is inserted for - each level of depth. Thus `%5_', for an account with four - parents, will insert twenty spaces. - -`(EXPR)' - Inserts the amount resulting from the value expression given in - parentheses. To insert five times the total value of an account, - for example, one could say `%12(5*O)'. Note: It's important to put - the five first in that expression, so that the commodity doesn't - get stripped from the total. - -`[DATEFMT]' - Inserts the result of formatting a transaction's date with a date - format string, exactly like those supported by `strftime'. For - example: `%[%Y/%m/%d %H:%M:%S]'. - -`S' - Insert the pathname of the file from which the entry's data was - read. - -`B' - Inserts the beginning character position of that entry within the - file. - -`b' - Inserts the beginning line of that entry within the file. - -`E' - Inserts the ending character position of that entry within the - file. - -`e' - Inserts the ending line of that entry within the file. - -`D' - By default, this is the same as `%[%Y/%m%/d]'. The date format - used can be changed at any time with the `-y' flag, however. - Using `%D' gives the user more control over the way dates are - output. - -`d' - This is the same as the `%D' option, unless the entry has an - effective date, in which case it prints - `[ACTUAL_DATE=EFFECtIVE_DATE]'. - -`X' - If a transaction has been cleared, this inserts `*' followed by a - space; otherwise nothing is inserted. - -`Y' - This is the same as `%X', except that it only displays a state - character if all of the member transactions have the same state. - -`C' - Inserts the checking number for an entry, in parentheses, followed - by a space; if none was specified, nothing is inserted. - -`P' - Inserts the payee related to a transaction. - -`a' - Inserts the optimal short name for an account. This is normally - used in balance reports. It prints a parent account's name if - that name has not been printed yet, otherwise it just prints the - account's name. - -`A' - Inserts the full name of an account. - -`W' - This is the same as `%A', except that it first displays the - transaction's state _if the entry's transaction states are not all - the same_, followed by the full account name. This is offered as - a printing optimization, so that combined with `%Y', only the - minimum amount of state detail is printed. - -`o' - Inserts the "optimized" form of a transaction's amount. This is - used by the print report. In some cases, this inserts nothing; in - others, it inserts the transaction amount and its cost. It's use - is not recommend unless you are modifying the print report. - -`n' - Inserts the note associated with a transaction, preceded by two - spaces and a semi-colon, if it exists. Thus, no none becomes an - empty string, while the note `foo' is substituted as ` ; foo'. - -`N' - Inserts the note associated with a transaction, if one exists. - -`/' - The `%/' construct is special. It separates a format string - between what is printed for the first transaction of an entry, and - what is printed for all subsequent transactions. If not used, the - same format string is used for all transactions. - - -File: ledger.info, Node: Value expressions, Next: Period expressions, Prev: Format strings, Up: Running Ledger - -2.5 Value expressions -===================== - -Value expressions are an expression language used by Ledger to -calculate values used by the program for many different purposes: - - 1. The values displayed in reports - - 2. For predicates (where truth is anything non-zero), to determine - which transactions are calculated (`-l') or displayed (`-d'). - - 3. For sorting criteria, to yield the sort key. - - 4. In the matching criteria used by automated transactions. - - Value expressions support most simple math and logic operators, in -addition to a set of one letter functions and variables. A function's -argument is whatever follows it. The following is a display predicate -that I use with the `balance' command: - - ledger -d /^Liabilities/?T<0:UT>100 balance - - The effect is that account totals are displayed only if: 1) A -Liabilities account has a total less than zero; or 2) the absolute -value of the account's total exceeds 100 units of whatever commodity -contains. If it contains multiple commodities, only one of them must -exceed 100 units. - - Display predicates are also very handy with register reports, to -constrain which entries are printed. For example, the following -command shows only entries from the beginning of the current month, -while still calculating the running balance based on all entries: - - ledger -d "d>[this month]" register checking - - This advantage to this command's complexity is that it prints the -running total in terms of all entries in the register. The following, -simpler command is similar, but totals only the displayed transactions: - - ledger -b "this month" register checking - -2.5.1 Variables ---------------- - -Below are the one letter variables available in any value expression. -For the register and print commands, these variables relate to -individual transactions, and sometimes the account affected by a -transaction. For the balance command, these variables relate to -accounts--often with a subtle difference in meaning. The use of each -variable for both is specified. - -`t' - This maps to whatever the user specified with `-t'. In a register - report, `-t' changes the value column; in a balance report, it has - no meaning by default. If `-t' was not specified, the current - report style's value expression is used. - -`T' - This maps to whatever the user specified with `-T'. In a register - report, `-T' changes the totals column; in a balance report, this - is the value given for each account. If `-T' was not specified, - the current report style's value expression is used. - -`m' - This is always the present moment/date. - -2.5.1.1 Transaction/account details -................................... - -`d' - A transaction's date, as the number of seconds past the epoch. - This is always "today" for an account. - -`a' - The transaction's amount; the balance of an account, without - considering children. - -`b' - The cost of a transaction; the cost of an account, without its - children. - -`v' - The market value of a transaction, or an account without its - children. - -`g' - The net gain (market value minus cost basis), for a transaction or - an account without its children. It is the same as `v-b'. - -`l' - The depth ("level") of an account. If an account has one parent, - it's depth is one. - -`n' - The index of a transaction, or the count of transactions affecting - an account. - -`X' - 1 if a transaction's entry has been cleared, 0 otherwise. - -`R' - 1 if a transaction is not virtual, 0 otherwise. - -`Z' - 1 if a transaction is not automated, 0 otherwise. - -2.5.1.2 Calculated totals -......................... - -`O' - The total of all transactions seen so far, or the total of an - account and all its children. - -`N' - The total count of transactions affecting an account and all its - children. - -`B' - The total cost of all transactions seen so far; the total cost of - an account and all its children. - -`V' - The market value of all transactions seen so far, or of an account - and all its children. - -`G' - The total net gain (market value minus cost basis), for a series of - transactions, or an account and its children. It is the same as - `V-B'. - -2.5.2 Functions ---------------- - -The available one letter functions are: - -`-' - Negates the argument. - -`U' - The absolute (unsigned) value of the argument. - -`S' - Strips the commodity from the argument. - -`A' - The arithmetic mean of the argument; `Ax' is the same as `x/n'. - -`P' - The present market value of the argument. The syntax `P(x,d)' is - supported, which yields the market value at time `d'. If no date - is given, then the current moment is used. - -2.5.3 Operators ---------------- - -The binary and ternary operators, in order of precedence, are: - - 1. `* /' - - 2. `+ -' - - 3. `! < > =' - - 4. `& | ?:' - -2.5.4 Complex expressions -------------------------- - -More complicated expressions are possible using: - -`NUM' - A plain integer represents a commodity-less amount. - -`{AMOUNT}' - An amount in braces can be any kind of amount supported by ledger, - with or without a commodity. Use this for decimal values. - -`/REGEXP/' - -`W/REGEXP/' - A regular expression that matches against an account's full name. - If a transaction, this will match against the account affected by - the transaction. - -`//REGEXP/' - -`p/REGEXP/' - A regular expression that matches against an entry's payee name. - -`///REGEXP/' - -`w/REGEXP/' - A regular expression that matches against an account's base name. - If a transaction, this will match against the account affected by - the transaction. - -`c/REGEXP/' - A regular expression that matches against the entry code (the text - that occurs between parentheses before the payee name). - -`e/REGEXP/' - A regular expression that matches against a transaction's note, or - comment field. - -`(EXPR)' - A sub-expression is nested in parenthesis. This can be useful - passing more complicated arguments to functions, or for overriding - the natural precedence order of operators. - -`[DATE]' - Useful specifying a date in plain terms. For example, you could - say `[2004/06/01]'. - - -File: ledger.info, Node: Period expressions, Next: File format, Prev: Value expressions, Up: Running Ledger - -2.6 Period expressions -====================== - -A period expression indicates a span of time, or a reporting interval, -or both. The full syntax is: - - [INTERVAL] [BEGIN] [END] - - The optional INTERVAL part may be any one of: - - every day - every week - every monthly - every quarter - every year - every N days # N is any integer - every N weeks - every N months - every N quarters - every N years - daily - weekly - biweekly - monthly - bimonthly - quarterly - yearly - - After the interval, a begin time, end time, both or neither may be -specified. As for the begin time, it can be either of: - - from - since - - The end time can be either of: - - to - until - - Where SPEC can be any of: - - 2004 - 2004/10 - 2004/10/1 - 10/1 - october - oct - this week # or day, month, quarter, year - next week - last week - - The beginning and ending can be given at the same time, if it spans a -single period. In that case, just use SPEC by itself. In that case, -the period `oct', for example, will cover all the days in october. The -possible forms are: - - - in - - Here are a few examples of period expressions: - - monthly - monthly in 2004 - weekly from oct - weekly from last month - from sep to oct - from 10/1 to 10/5 - monthly until 2005 - from apr - until nov - last oct - weekly last august - - -File: ledger.info, Node: File format, Next: Some typical queries, Prev: Period expressions, Up: Running Ledger - -2.7 File format -=============== - -The ledger file format is quite simple, but also very flexible. It -supports many options, though typically the user can ignore most of -them. They are summarized below. - - The initial character of each line determines what the line means, -and how it should be interpreted. Allowable initial characters are: - -`NUMBER' - A line beginning with a number denotes an entry. It may be - followed by any number of lines, each beginning with whitespace, - to denote the entry's account transactions. The format of the - first line is: - - DATE[=EDATE] [*|!] [(CODE)] DESC - - If `*' appears after the date (with optional effective date), it - indicates the entry is "cleared", which can mean whatever the user - wants it t omean. If `!' appears after the date, it indicates d - the entry is "pending"; i.e., tentatively cleared from the user's - point of view, but not yet actually cleared. If a `CODE' appears - in parentheses, it may be used to indicate a check number, or the - type of the transaction. Following these is the payee, or a - description of the transaction. - - The format of each following transaction is: - - ACCOUNT AMOUNT [; NOTE] - - The `ACCOUNT' may be surrounded by parentheses if it is a virtual - transactions, or square brackets if it is a virtual transactions - that must balance. The `AMOUNT' can be followed by a per-unit - transaction cost, by specifying ` AMOUNT', or a complete - transaction cost with `@ AMOUNT'. Lastly, the `NOTE' may specify - an actual and/or effective date for the transaction by using the - syntax `[ACTUAL_DATE]' or `[=EFFECTIVE_DATE]' or - `[ACTUAL_DATE=EFFECtIVE_DATE]'. - -`=' - An automated entry. A value expression must appear after the equal - sign. - - After this initial line there should be a set of one or more - transactions, just as if it were normal entry. If the amounts of - the transactions have no commodity, they will be applied as - modifiers to whichever real transaction is matched by the value - expression. - -`~' - A period entry. A period expression must appear after the tilde. - - After this initial line there should be a set of one or more - transactions, just as if it were normal entry. - -`!' - A line beginning with an exclamation mark denotes a command - directive. It must be immediately followed by the command word. - The supported commands are: - - `!include' - Include the stated ledger file. - - `!account' - The account name is given is taken to be the parent of all - transactions that follow, until `!end' is seen. - - `!end' - Ends an account block. - -`;' - A line beginning with a colon indicates a comment, and is ignored. - -`Y' - If a line begins with a capital Y, it denotes the year used for all - subsequent entries that give a date without a year. The year - should appear immediately after the Y, for example: `Y2004'. This - is useful at the beginning of a file, to specify the year for that - file. If all entries specify a year, however, this command has no - effect. - -`P' - Specifies a historical price for a commodity. These are usually - found in a pricing history file (see the `-Q' option). The syntax - is: - P DATE SYMBOL PRICE - -`N SYMBOL' - Indicates that pricing information is to be ignored for a given - symbol, nor will quotes ever be downloaded for that symbol. Useful - with a home currency, such as the dollar ($). It is recommended - that these pricing options be set in the price database file, which - defaults to `~/.pricedb'. The syntax for this command is: - N SYMBOL - -`D AMOUNT' - Specifies the default commodity to use, by specifying an amount in - the expected format. The `entry' command will use this commodity - as the default when none other can be determined. This command - may be used multiple times, to set the default flags for different - commodities; whichever is seen last is used as the default - commodity. For example, to set US dollars as the default - commodity, while also setting the thousands flag and decimal flag - for that commodity, use: - D $1,000.00 - -`C AMOUNT1 = AMOUNT2' - Specifies a commodity conversion, where the first amount is given - to be equivalent to the second amount. The first amount should - use the decimal precision desired during reporting: - C 1.00 Kb = 1024 bytes - -`i, o, b, h' - These four relate to timeclock support, which permits ledger to - read timelog files. See the timeclock's documentation for more - info on the syntax of its timelog files. - - -File: ledger.info, Node: Some typical queries, Next: Budgeting and forecasting, Prev: File format, Up: Running Ledger - -2.8 Some typical queries -======================== - -A query such as the following shows all expenses since last October, -sorted by total: - - ledger -b "last oct" -s -S T bal ^expenses - - From left to right the options mean: Show entries since October, -2003; show all sub-accounts; sort by the absolute value of the total; -and report the balance for all expenses. - -2.8.1 Reporting monthly expenses --------------------------------- - -The following query makes it easy to see monthly expenses, with each -month's expenses sorted by the amount: - - ledger -M --period-sort t reg ^expenses - - Now, you might wonder where the money came from to pay for these -things. To see that report, add `-r', which shows the "related -account" transactions: - - ledger -M --period-sort t -r reg ^expenses - - But maybe this prints too much information. You might just want to -see how much you're spending with your MasterCard. That kind of query -requires the use of a display predicate, since the transactions -calculated must match `^expenses', while the transactions displayed -must match `mastercard'. The command would be: - - ledger -M -r -d /mastercard/ reg ^expenses - - This query says: Report monthly subtotals; report the "related -account" transactions; display only related transactions whose account -matches `mastercard', and base the calculation on transactions matching -`^expenses'. - - This works just as well for report the overall total, too: - - ledger -s -r -d /mastercard/ reg ^expenses - - The `-s' option subtotals all transactions, just as `-M' subtotaled -by the month. The running total in both cases is off, however, since a -display expression is being used. - -2.8.2 Visualizing with Gnuplot ------------------------------- - -If you have `Gnuplot' installed, you can graph any of the above -register reports. The script to do this is included in the ledger -distribution, and is named `scripts/report'. Install `report' anywhere -along your `PATH', and then use `report' instead of `ledger' when doing -a register report. The only thing to keep in mind is that you must -specify `-j' or `-J' to indicate whether Gnuplot should plot the -amount, or the running total. For example, this command plots total -monthly expenses made on your MasterCard. - - report -j -M -r -d /mastercard/ reg ^expenses - - The `report' script is a very simple Bourne shell script, that -passes a set of scripted commands to Gnuplot. Feel free to modify the -script to your liking, since you may prefer histograms to line plots, -for example. - -2.8.2.1 Typical plots -..................... - -Here are some useful plots: - - report -j -M reg ^expenses # monthly expenses - report -J reg checking # checking account balance - report -J reg ^income ^expenses # cash flow report - - # net worth report, ignoring non-$ transactions - - report -J -l "Ua>={\$0.01}" reg ^assets ^liab - - # net worth report starting last February. the use of a display - # predicate (-d) is needed, otherwise the balance will start at - # zero, and thus the y-axis will not reflect the true balance - - report -J -l "Ua>={\$0.01}" -d "d>=[last feb]" reg ^assets ^liab - - The last report uses both a calculation predicate (`-l') and a -display predicate (`-d'). The calculation predicates limits the report -to transactions whose amount is greater than $1 (which can only happen -if the transaction amount is in dollars). The display predicate limits -the entries _displayed_ to just those since last February, even those -entries from before then will be computed as part of the balance. - - -File: ledger.info, Node: Budgeting and forecasting, Prev: Some typical queries, Up: Running Ledger - -2.9 Budgeting and forecasting -============================= - -2.9.1 Budgeting ---------------- - -Keeping a budget allows you to pay closer attention to your income and -expenses, by reporting how far your actual financial activity is from -your expectations. - - To start keeping a budget, put some period entries at the top of your -ledger file. A period entry is almost identical to a regular entry, -except that it begins with a tilde and has a period expression in place -of a payee. For example: - - ~ Monthly - Expenses:Rent $500.00 - Expenses:Food $450.00 - Expenses:Auto:Gas $120.00 - Expenses:Insurance $150.00 - Expenses:Phone $125.00 - Expenses:Utilities $100.00 - Expenses:Movies $50.00 - Expenses $200.00 ; all other expenses - Assets - - ~ Yearly - Expenses:Auto:Repair $500.00 - Assets - - These two period entries give the usual monthly expenses, as well as -one typical yearly expense. For help on finding out what your average -monthly expense is for any category, use a command like: - - ledger -p "this year" -MAs bal ^expenses - - The reported totals are the current year's average for each account. - - Once these period entries are defined, creating a budget report is as -easy as adding `--budget' to the command-line. For example, a typical -monthly expense report would be: - - ledger -M reg ^exp - - To see the same report balanced against your budget, use: - - ledger --budget -M reg ^exp - - A budget report includes only those accounts that appear in the -budget. To see all expenses balanced against the budget, use -`--add-budget'. You can even see only the unbudgeted expenses using -`--unbudgeted': - - ledger --unbudgeted -M reg ^exp - - You can also use these flags with the `balance' command. - -2.9.2 Forecasting ------------------ - -Sometimes it's useful to know what your finances will look like in the -future, such as determining when an account will reach zero. Ledger -makes this easy to do, using the same period entries as are used for -budgeting. An example forecast report can be generated with: - - ledger --forecast "T>{\$-500.00}" register ^assets ^liabilities - - This report continues outputting transactions until the running total -is greater than $-500.00. A final transaction is always output, to -show you what the total afterwards would be. - - Forecasting can also be used with the balance report, but by date -only, and not against the running total: - - ledger --forecast "d<[2010]" bal ^assets ^liabilities - - -File: ledger.info, Node: Keeping a ledger, Next: Using XML, Prev: Running Ledger, Up: Top - -3 Keeping a ledger -****************** - -The most important part of accounting is keeping a good ledger. If you -have a good ledger, tools can be written to work whatever -mathematically tricks you need to better understand your spending -patterns. Without a good ledger, no tool, however smart, can help you. - - The Ledger program aims at making ledger entry as simple as possible. -Since it is a command-line tool, it does not provide a user interface -for keeping a ledger. If you like, you may use GnuCash to maintain -your ledger, in which case the Ledger program will read GnuCash's data -files directly. In that case, read the GnuCash manual now, and skip to -the next chapter. - - If you are not using GnuCash, but a text editor to maintain your -ledger, read on. Ledger has been designed to make data entry as simple -as possible, by keeping the ledger format easy, and also by -automagically determining as much information as possible based on the -nature of your entries. - - For example, you do not need to tell Ledger about the accounts you -use. Any time Ledger sees a transaction involving an account it knows -nothing about, it will create it. If you use a commodity that is new -to Ledger, it will create that commodity, and determine its display -characteristics (placement of the symbol before or after the amount, -display precision, etc) based on how you used the commodity in the -transaction. - - Here is the Pacific Bell example from above, given as a Ledger -transaction: - - 9/29 (100) Pacific Bell - Expenses:Utilities:Phone $23.00 - Assets:Checking $-23.00 - - As you can see, it is very similar to what would be written on paper, -minus the computed balance totals, and adding in account names that -work better with Ledger's scheme of things. In fact, since Ledger is -smart about many things, you don't need to specify the balanced amount, -if it is the same as the first line: - - 9/29 (100) Pacific Bell - Expenses:Utilities:Phone $23.00 - Assets:Checking - - For this entry, Ledger will figure out that $-23.00 must come from -`Assets:Checking' in order to balance the entry. - -* Menu: - -* Stating where money goes:: -* Assets and Liabilities:: -* Commodities and Currencies:: -* Accounts and Inventories:: -* Understanding Equity:: -* Dealing with Petty Cash:: -* Working with multiple funds and accounts:: -* Archiving previous years:: -* Virtual transactions:: -* Automated transactions:: -* Using Emacs to Keep Your Ledger:: -* Using GnuCash to Keep Your Ledger:: -* Using timeclock to record billable time:: - - -File: ledger.info, Node: Stating where money goes, Next: Assets and Liabilities, Prev: Keeping a ledger, Up: Keeping a ledger - -3.1 Stating where money goes -============================ - -Accountants will talk of "credits" and "debits", but the meaning is -often different from the layman's understanding. To avoid confusion, -Ledger uses only subtractions and additions, although the underlying -intent is the same as standard accounting principles. - - Recall that every transaction will involve two or more accounts. -Money is transferred from one or more accounts to one or more other -accounts. To record the transaction, an amount is _subtracted_ from -the source accounts, and _added_ to the target accounts. - - In order to write a Ledger entry correctly, you must determine where -the money comes from and where it goes to. For example, when you are -paid a salary, you must add money to your bank account and also -subtract it from an income account: - - 9/29 My Employer - Assets:Checking $500.00 - Income:Salary $-500.00 - - Why is the Income a negative figure? When you look at the balance -totals for your ledger, you may be surprised to see that Expenses are a -positive figure, and Income is a negative figure. It may take some -getting used to, but to properly use a general ledger you must think in -terms of how money moves. Rather than Ledger "fixing" the minus signs, -let's understand why they are there. - - When you earn money, the money has to come from somewhere. Let's -call that somewhere "society". In order for society to give you an -income, you must take money away (withdraw) from society in order to -put it into (make a payment to) your bank. When you then spend that -money, it leaves your bank account (a withdrawal) and goes back to -society (a payment). This is why Income will appear negative--it -reflects the money you have drawn from society--and why Expenses will -be positive--it is the amount you've given back. These additions and -subtractions will always cancel each other out in the end, because you -don't have the ability to create new money: it must always come from -somewhere, and in the end must always leave. This is the beginning of -economy, after which the explanation gets terribly difficult. - - Based on that explanation, here's another way to look at your balance -report: every negative figure means that that account or person or -place has less money now than when you started your ledger; and every -positive figure means that that account or person or place has more -money now that when you started your ledger. Make sense? - - -File: ledger.info, Node: Assets and Liabilities, Next: Commodities and Currencies, Prev: Stating where money goes, Up: Keeping a ledger - -3.2 Assets and Liabilities -========================== - -Assets are money that you have, and Liabilities are money that you owe. -"Liabilities" is just a more inclusive name for Debts. - - An Asset is typically increased by transferring money from an Income -account, such as when you get paid. Here is a typical entry: - - 2004/09/29 My Employer - Assets:Checking $500.00 - Income:Salary - - Money, here, comes from an Income account belonging to "My -Employer", and is transferred to your checking account. The money is -now yours, which makes it an Asset. - - Liabilities track money owed to others. This can happen when you -borrow money to buy something, or if you owe someone money. Here is an -example of increasing a MasterCard liability by spending money with it: - - 2004/09/30 Restaurant - Expenses:Dining $25.00 - Liabilities:MasterCard - - The Dining account balance now shows $25 spent on Dining, and a -corresponding $25 owed on the MasterCard--and therefore shown as -$-25.00. The MasterCard liability shows up as negative because it -offsets the value of your assets. - - The combined total of your Assets and Liabilities is your net worth. -So to see your current net worth, use this command: - - ledger balance ^assets ^liabilities - - Relatedly, your Income accounts show up negative, because they -transfer money _from_ an account in order to increase your assets. -Your Expenses show up positive because that is where the money went to. -The combined total of Income and Expenses is your cash flow. A -positive cash flow means you are spending more than you make, since -income is always a negative figure. To see your current cash flow, use -this command: - - ledger balance ^income ^expenses - - Another common question to ask of your expenses is: How much do I -spend each month on X? Ledger provides a simple way of displaying -monthly totals for any account. Here is an example that summarizes -your monthly automobile expenses: - - ledger -M register expenses:auto - - This assumes, of course, that you use account names like -`Expenses:Auto:Gas' and `Expenses:Auto:Repair'. - -3.2.1 Tracking reimbursable expenses ------------------------------------- - -Sometimes you will want to spend money on behalf of someone else, which -will eventually get repaid. Since the money is still "yours", it is -really an asset. And since the expenditure was for someone else, you -don't want it contaminating your Expenses reports. You will need to -keep an account for tracking reimbursements. - - This is fairly easy to do in ledger. When spending the money, spend -it _to_ your Assets:Reimbursements, using a different account for each -person or business that you spend money for. For example: - - 2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard - - This shows $100.00 spent on a MasterCard at Circuit City, with the -expense was made on behalf of Company XYZ. Later, when Company XYZ -pays the amount back, the money will transfer from that reimbursement -account back to a regular asset account: - - 2004/09/29 Company XYZ - Assets:Checking $100.00 - Assets:Reimbursements:Company XYZ - - This deposits the money owed from Company XYZ into a checking -account, presumably because they paid the amount back with a check. - - But what to do if you run your own business, and you want to keep -track of expenses made on your own behalf, while still tracking -everything in a single ledger file? This is more complex, because you -need to track two separate things: 1) The fact that the money should be -reimbursed to you, and 2) What the expense account was, so that you can -later determine where your company is spending its money. - - This kind of transaction is best handled with mirrored transactions -in two different files, one for your personal accounts, and one for your -company accounts. But keeping them in one file involves the same kinds -of transactions, so those are what is shown here. First, the personal -entry, which shows the need for reimbursement: - - 2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard - - This is the same as above, except that you own Company XYZ, and are -keeping track of its expenses in the same ledger file. This entry -should be immediately followed by an equivalent entry, which shows the -kind of expense, and also notes the fact that $100.00 is now payable to -you: - - 2004/09/29 Circuit City - Company XYZ:Expenses:Computer:Software $100.00 - Company XYZ:Accounts Payable:Your Name - - This second entry shows that Company XYZ has just spent $100.00 on -software, and that this $100.00 came from Your Name, which must be paid -back. - - These two entries can also be merged, to make things a little -clearer. Note that all amounts must be specified now: - - 2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard $-100.00 - Company XYZ:Expenses:Computer:Software $100.00 - Company XYZ:Accounts Payable:Your Name $-100.00 - - To "pay back" the reimbursement, just reverse the order of -everything, except this time drawing the money from a company asset, -paying it to accounts payable, and then drawing it again from the -reimbursement account, and paying it to your personal asset account. -It's easier shown than said: - - 2004/10/15 Company XYZ - Assets:Checking $100.00 - Assets:Reimbursements:Company XYZ $-100.00 - Company XYZ:Accounts Payable:Your Name $100.00 - Company XYZ:Assets:Checking $-100.00 - - And now the reimbursements account is paid off, accounts payable is -paid off, and $100.00 has been effectively transferred from the -company's checking account to your personal checking account. The -money simply "waited"--in both `Assets:Reimbursements:Company XYZ', and -`Company XYZ:Accounts Payable:Your Name'--until such time as it could -be paid off. - - The value of tracking expenses from both sides like that is that you -do not contaminate your personal expense report with expenses made on -behalf of others, while at the same time making it possible to generate -accurate reports of your company's expenditures. It is more verbose -than just paying for things with your personal assets, but it gives you -a very accurate information trail. - - The advantage to keep these doubled entries together is that they -always stay in sync. The advantage to keeping them apart is that it -clarifies the transfer's point of view. To keep the transactions in -separate files, just separate the two entries that were joined above. -For example, for both the expense and the pay-back shown above, the -following four entries would be created. Two in your personal ledger -file: - - 2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard $-100.00 - - 2004/10/15 Company XYZ - Assets:Checking $100.00 - Assets:Reimbursements:Company XYZ $-100.00 - - And two in your company ledger file: - - !account Company XYZ - - 2004/09/29 Circuit City - Expenses:Computer:Software $100.00 - Accounts Payable:Your Name $-100.00 - - 2004/10/15 Company XYZ - Accounts Payable:Your Name $100.00 - Assets:Checking $-100.00 - - !end - - (Note: The `!account' above means that all accounts mentioned in the -file are children of that account. In this case it means that all -activity in the file relates to Company XYZ). - - After creating these entries, you will always know that $100.00 was -spent using your MasterCard on behalf of Company XYZ, and that Company -XYZ spent the money on computer software and paid it back about two -weeks later. - - -File: ledger.info, Node: Commodities and Currencies, Next: Accounts and Inventories, Prev: Assets and Liabilities, Up: Keeping a ledger - -3.3 Commodities and Currencies -============================== - -Ledger makes no assumptions about the commodities you use; it only -requires that you specify a commodity. The commodity may be any -non-numeric string that does not contain a period, comma, forward slash -or at-sign. It may appear before or after the amount, although it is -assumed that symbols appearing before the amount refer to currencies, -while non-joined symbols appearing after the amount refer to -commodities. Here are some valid currency and commodity specifiers: - - $20.00 ; currency: twenty US dollars - 40 AAPL ; commodity: 40 shares of Apple stock - 60 DM ; currency: 60 Deutsch Mark - £50 ; currency: 50 British pounds - 50 EUR ; currency: 50 Euros (or use appropriate symbol) - - Ledger will examine the first use of any commodity to determine how -that commodity should be printed on reports. It pays attention to -whether the name of commodity was separated from the amount, whether it -came before or after, the precision used in specifying the amount, -whether thousand marks were used, etc. This is done so that printing -the commodity looks the same as the way you use it. - - An account may contain multiple commodities, in which case it will -have separate totals for each. For example, if your brokerage account -contains both cash, gold, and several stock quantities, the balance -might look like: - - $200.00 - 100.00 AU - AAPL 40 - BORL 100 - FEQTX 50 Assets:Brokerage - - This balance report shows how much of each commodity is in your -brokerage account. - - Sometimes, you will want to know the current street value of your -balance, and not the commodity totals. For this to happen, you must -specify what the current price is for each commodity. The price can be -any commodity, in which case the balance will be computed in terms of -that commodity. The usual way to specify prices is with a price -history file, which might look like this: - - P 2004/06/21 02:18:01 FEQTX $22.49 - P 2004/06/21 02:18:01 BORL $6.20 - P 2004/06/21 02:18:02 AAPL $32.91 - P 2004/06/21 02:18:02 AU $400.00 - - Specify the price history to use with the `--price-db' option, with -the `-V' option to report in terms of current market value: - - ledger --price-db prices.db -V balance brokerage - - The balance for your brokerage account will be reported in US -dollars, since the prices database uses that currency. - - $40880.00 Assets:Brokerage - - You can convert from any commodity to any other commodity. Let's say -you had $5000 in your checking account, and for whatever reason you -wanted to know many ounces of gold that would buy, in terms of the -current price of gold: - - ledger -T "{1 AU}*(O/P{1 AU})" balance checking - - Although the total expression appears complex, it is simply saying -that the reported total should be in multiples of AU units, where the -quantity is the account total divided by the price of one AU. Without -the initial multiplication, the reported total would still use the -dollars commodity, since multiplying or dividing amounts always keeps -the left value's commodity. The result of this command might be: - - 14.01 AU Assets:Checking - -3.3.1 Commodity price histories -------------------------------- - -Whenever a commodity is purchased using a different commodity (such as -a share of common stock using dollars), it establishes a price for that -commodity on that day. It is also possible, by recording price details -in a ledger file, to specify other prices for commodities at any given -time. Such price entries might look like those below: - - P 2004/06/21 02:17:58 TWCUX $27.76 - P 2004/06/21 02:17:59 AGTHX $25.41 - P 2004/06/21 02:18:00 OPTFX $39.31 - P 2004/06/21 02:18:01 FEQTX $22.49 - P 2004/06/21 02:18:02 AAPL $32.91 - - By default, ledger will not consider commodity prices when generating -its various reports. It will always report balances in terms of the -commodity total, rather than the current value of those commodities. -To enable pricing reports, use one of the commodity reporting options. - -3.3.2 Commodity equivalencies ------------------------------ - -Sometimes a commodity has several forms which are all equivalent. An -example of this is time. Whether tracked in terms of minutes, hours or -days, it should be possible to convert between the various forms. -Doing this requires the use of commodity equivalencies. - - For example, you might have the following two transactions, one which -transfers an hour of time into a `Billable' account, and another which -decreases the same account by ten minutes. The resulting report will -indicate that fifty minutes remain: - - 2005/10/01 Work done for company - Billable:Client 1h - Project:XYZ - - 2005/10/02 Return ten minutes to the project - Project:XYZ 10m - Billable:Client - - Reporting the balance for this ledger file produces: - - 50.0m Billable:Client - -50.0m Project:XYZ - - This example works because ledger already knows how to handle -seconds, minutes and hours, as part of its time tracking support. -Defining other equivalencies is simple. The following is an example -that creates data equivalencies, helpful for tracking bytes, kilobytes, -megabytes, and more: - - C 1.00 Kb = 1024 b - C 1.00 Mb = 1024 Kb - C 1.00 Gb = 1024 Mb - C 1.00 Tb = 1024 Gb - - Each of these definitions correlates a commodity (such as `Kb') and -a default precision, with a certain quantity of another commodity. In -the above example, kilobytes are reporetd with two decimal places of -precision and each kilobyte is equal to 1024 bytes. - - Equivalency chains can be as long as desired. Whenever a commodity -would report as a decimal amount (less than `1.00'), the next smallest -commodity is used. If a commodity could be reported in terms of a -higher commodity without resulting to a partial fraction, then the -larger commodity is used. - - -File: ledger.info, Node: Accounts and Inventories, Next: Understanding Equity, Prev: Commodities and Currencies, Up: Keeping a ledger - -3.4 Accounts and Inventories -============================ - -Since Ledger's accounts and commodity system is so flexible, you can -have accounts that don't really exist, and use commodities that no one -else recognizes. For example, let's say you are buying and selling -various items in EverQuest, and want to keep track of them using a -ledger. Just add items of whatever quantity you wish into your -EverQuest account: - - 9/29 Get some stuff at the Inn - Places:Black's Tavern -3 Apples - Places:Black's Tavern -5 Steaks - EverQuest:Inventory - - Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The -amounts are negative, because you are taking _from_ Black's Tavern in -order to add to your Inventory account. Note that you don't have to -use `Places:Black's Tavern' as the source account. You could use -`EverQuest:System' to represent the fact that you acquired them online. -The only purpose for choosing one kind of source account over another -is for generate more informative reports later on. The more you know, -the better analysis you can perform. - - If you later sell some of these items to another player, the entry -would look like: - - 10/2 Sturm Brightblade - EverQuest:Inventory -2 Steaks - EverQuest:Inventory 15 Gold - - Now you've turned 2 steaks into 15 gold, courtesy of your customer, -Sturm Brightblade. - - -File: ledger.info, Node: Understanding Equity, Next: Dealing with Petty Cash, Prev: Accounts and Inventories, Up: Keeping a ledger - -3.5 Understanding Equity -======================== - -The most confusing entry in any ledger will be your equity account-- -because starting balances can't come out of nowhere. - - When you first start your ledger, you will likely already have money -in some of your accounts. Let's say there's $100 in your checking -account; then add an entry to your ledger to reflect this amount. -Where will money come from? The answer: your equity. - - 10/2 Opening Balance - Assets:Checking $100.00 - Equity:Opening Balances - - But what is equity? You may have heard of equity when people talked -about house mortgages, as "the part of the house that you own". -Basically, equity is like the value of something. If you own a car -worth $5000, then you have $5000 in equity in that car. In order to -turn that car (a commodity) into a cash flow, or a credit to your bank -account, you will have to debit the equity by selling it. - - When you start a ledger, you are probably already worth something. -Your net worth is your current equity. By transferring the money in -the ledger from your equity to your bank accounts, you are crediting -the ledger account based on your prior equity. That is why, when you -look at the balance report, you will see a large negative number for -Equity that never changes: Because that is what you were worth (what -you debited from yourself in order to start the ledger) before the -money started moving around. If the total positive value of your -assets is greater than the absolute value of your starting equity, it -means you are making money. - - Clear as mud? Keep thinking about it. Until you figure it out, put -`-Equity' at the end of your balance command, to remove the confusing -figure from the total. - - -File: ledger.info, Node: Dealing with Petty Cash, Next: Working with multiple funds and accounts, Prev: Understanding Equity, Up: Keeping a ledger - -3.6 Dealing with Petty Cash -=========================== - -Something that stops many people from keeping a ledger at all is the -insanity of tracking small cash expenses. They rarely generate a -receipt, and there are often a lot of small transactions, rather than a -few large ones, as with checks. - - One solution is: don't bother. Move your spending to a debit card, -but in general ignore cash. Once you withdraw it from the ATM, mark it -as already spent to an `Expenses:Cash' category: - - 2004/03/15 ATM - Expenses:Cash $100.00 - Assets:Checking - - If at some point you make a large cash expense that you want to -track, just "move" the amount of the expense from `Expenses:Cash' into -the target account: - - 2004/03/20 Somebody - Expenses:Food $65.00 - Expenses:Cash - - This way, you can still track large cash expenses, while ignoring all -of the smaller ones. - - -File: ledger.info, Node: Working with multiple funds and accounts, Next: Archiving previous years, Prev: Dealing with Petty Cash, Up: Keeping a ledger - -3.7 Working with multiple funds and accounts -============================================ - -There are situations when the accounts you're tracking are different -between your clients and the financial institutions where money is -kept. An example of this is working as the treasurer for a religious -institution. From the secular point of view, you might be working with -three different accounts: - - * Checking - - * Savings - - * Credit Card - - From a religious point of view, the community expects to divide its -resources into multiple "funds", from which it makes purchases or -reserves resources for later: - - * School fund - - * Building fund - - * Community fund - - The problem with this kind of setup is that when you spend money, it -comes from two or more places at once: the account and the fund. And -yet, the correlation of amounts between funds and accounts is rarely -one-to-one. What if the school fund has `$500.00', but `$400.00' of -that comes from Checking, and `$100.00' from Savings? - - Traditional finance packages require that the money reside in only -one place. But there are really two "views" of the data: from the -account point of view and from the fund point of view - yet both sets -should reflect the same overall expenses and cash flow. It's simply -where the money resides that differs. - - This situation can be handled one of two ways. The first is using -virtual transactions to represent the fact that money is moving to and -from two kind of accounts at the same time: - - 2004/03/20 Contributions - Assets:Checking $500.00 - Income:Donations - - 2004/03/25 Distribution of donations - [Funds:School] $300.00 - [Funds:Building] $200.00 - [Assets:Checking] $-500.00 - - The use of square brackets in the second entry ensures that the -virtual transactions balance to zero. Now money can be spent directly -from a fund at the same time as money is drawn from a physical account: - - 2004/03/25 Payment for books (paid from Checking) - Expenses:Books $100.00 - Assets:Checking $-100.00 - (Funds:School) $-100.00 - - When reports are generated, by default they'll appear in terms of the -funds. In this case, you will likely want to mask out your `Assets' -account, because otherwise the balance won't make much sense: - - ledger bal -^Assets - - If the `--real' option is used, the report will be in terms of the -real accounts: - - ledger --real bal - - If more asset accounts are needed as the source of a transaction, -just list them as you would normally, for example: - - 2004/03/25 Payment for books (paid from Checking) - Expenses:Books $100.00 - Assets:Checking $-50.00 - Liabilities:Credit Card $-50.00 - (Funds:School) $-100.00 - - The second way of tracking funds is to use entry codes. In this -respect the codes become like virtual accounts that embrace the entire -set of transactions. Basically, we are associating an entry with a -fund by setting its code. Here are two entries that desposit money -into, and spend money from, the `Funds:School' fund: - - 2004/03/25 (Funds:School) Donations - Assets:Checking $100.00 - Income:Donations - - 2004/04/25 (Funds:School) Payment for books - Expenses:Books $50.00 - Assets:Checking - - Note how the accounts now relate only to the real accounts, and any -balance or registers reports will reflect this. That the entries -relate to a particular fund is kept only in the code. - - How does this become a fund report? By using the `--code-as-payee' -option, you can generate a register report where the payee for each -transaction shows the code. Alone, this is not terribly interesting; -but when combined with the `--by-payee' option, you will now see -account subtotals for any transactions related to a specific fund. So, -to see the current monetary balances of all funds, the command would be: - - ledger --code-as-payee -P reg ^Assets - - Or to see a particular funds expenses, the `School' fund in this -case: - - ledger --code-as-payee -P reg ^Expenses -- School - - Both approaches yield different kinds of flexibility, depending on -how you prefer to think of your funds: as virtual accounts, or as tags -associated with particular entries. Your own tastes will decide which -is best for your situation. - - -File: ledger.info, Node: Archiving previous years, Next: Virtual transactions, Prev: Working with multiple funds and accounts, Up: Keeping a ledger - -3.8 Archiving previous years -============================ - -After a while, your ledger can get to be pretty large. While this will -not slow down the ledger program much--it's designed to process ledger -files very quickly--things can start to feel "messy"; and it's a -universal complaint that when finances feel messy, people avoid them. - - Thus, archiving the data from previous years into their own files can -offer a sense of completion, and freedom from the past. But how to -best accomplish this with the ledger program? There are two commands -that make it very simple: `print', and `equity'. - - Let's take an example file, with data ranging from year 2000 until -2004. We want to archive years 2000 and 2001 to their own file, -leaving just 2003 and 2004 in the current file. So, use `print' to -output all the earlier entries to a file called `ledger-old.dat': - - ledger -f ledger.dat -b 2000 -e 2001 print > ledger-old.dat - - To delete older data from the current ledger file, use `print' -again, this time specifying year 2002 as the starting date: - - ledger -f ledger.dat -b 2002 print > x - mv x ledger.dat - - However, now the current file contains _only_ transactions from 2002 -onward, which will not yield accurate present-day balances, because the -net income from previous years is no longer being tallied. To -compensate for this, we must append an equity report for the old ledger -at the beginning of the new one: - - ledger -f ledger-old.dat equity > equity.dat - cat equity.dat ledger.dat > x - mv x ledger.dat - rm equity.dat - - Now the balances reported from `ledger.dat' are identical to what -they were before the data was split. - - How often should you split your ledger? You never need to, if you -don't want to. Even eighty years of data will not slow down ledger -much--and that's just using present day hardware! Or, you can keep the -previous and current year in one file, and each year before that in its -own file. It's really up to you, and how you want to organize your -finances. For those who also keep an accurate paper trail, it might be -useful to archive the older years to their own files, then burn those -files to a CD to keep with the paper records--along with any electronic -statements received during the year. In the arena of organization, -just keep in mind this maxim: Do whatever keeps you doing it. - - -File: ledger.info, Node: Virtual transactions, Next: Automated transactions, Prev: Archiving previous years, Up: Keeping a ledger - -3.9 Virtual transactions -======================== - -A virtual transaction is when you, in your mind, see money as moving to -a certain place, when in reality that money has not moved at all. -There are several scenarios in which this type of tracking comes in -handy, and each of them will be discussed in detail. - - To enter a virtual transaction, surround the account name in -parentheses. This form of usage does not need to balance. However, if -you want to ensure the virtual transaction balances with other virtual -transactions in the same entry, use square brackets. For example: - - 10/2 Paycheck - Assets:Checking $1000.00 - Income:Salary $-1000.00 - (Debt:Alimony) $200.00 - - In this example, after receiving a paycheck an alimony debt is -increased--even though no money has moved around yet. - - 10/2 Paycheck - Assets:Checking $1000.00 - Income:Salary $-1000.00 - [Savings:Trip] $200.00 - [Assets:Checking] $-200.00 - - In this example, $200 has been deducted from checking toward savings -for a trip. It will appear as though the money has been moved from the -account into `Savings:Trip', although no money has actually moved -anywhere. - - When balances are displayed, virtual transactions will be factored -in. To view balances without any virtual balances factored in, using -the `-R' flag, for "reality". - - -File: ledger.info, Node: Automated transactions, Next: Using Emacs to Keep Your Ledger, Prev: Virtual transactions, Up: Keeping a ledger - -3.10 Automated transactions -=========================== - -As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire -assets. It is similar to tithing for Jews and Christians, or to Zakát -for Muslims. The exact details of computing Huqúqu'lláh are somewhat -complex, but if you have further interest, please consult the Web. - - Ledger makes this otherwise difficult law very easy. Just set up an -automated transaction at the top of your ledger file: - - ; This automated entry will compute Huqúqu'lláh based on this - ; journal's transactions. Any that match will affect the - ; Liabilities:Huququ'llah account by 19% of the value of that - ; transaction. - - = /^(?:Income:|Expenses:(?:Business|Rent$|Furnishings|Taxes|Insurance))/ - (Liabilities:Huququ'llah) 0.19 - - This automated transaction works by looking at each transaction in -the ledger file. If any match the given value expression, 19% of the -transaction's value is applied to the `Liabilities:Huququ'llah' -account. So, if $1000 is earned from `Income:Salary', $190 is added to -`Liabilities:Huqúqu'lláh'; if $1000 is spent on Rent, $190 is -subtracted. The ultimate balance of Huqúqu'lláh reflects how much is -owed in order to fulfill one's obligation to Huqúqu'lláh. When ready -to pay, just write a check to cover the amount shown in -`Liabilities:Huququ'llah'. That entry would look like: - - 2003/01/01 (101) Baha'i Huqúqu'lláh Trust - Liabilities:Huququ'llah $1,000.00 - Assets:Checking - - That's it. To see how much Huqúq is currently owed based on your -ledger entries, use: - - ledger balance Liabilities:Huquq - - This works fine, but omits one aspect of the law: that Huquq is only -due once the liability exceeds the value of 19 mithqáls of gold (which -is roughly 2.22 ounces). So what we want is for the liability to -appear in the balance report only when it exceeds the present day value -of 2.22 ounces of gold. This can be accomplished using the command: - - ledger -Q -t "/Liab.*Huquq/?(a/P{2.22 AU}<={-1.0}&a):a" -s bal liab - - With this command, the current price for gold is downloaded, and the -Huqúqu'lláh is reported only if its value exceeds that of 2.22 ounces -of gold. If you wish the liability to be reflected in the parent -subtotal either way, use this instead: - - ledger -Q -T "/Liab.*Huquq/?(O/P{2.22 AU}<={-1.0}&O):O" -s bal liab - - In some cases, you may wish to refer to the account of whichever -transaction matched your automated entry's value expression. To do -this, use the special account name `$account': - - = /^Some:Long:Account:Name/ - [$account] -0.10 - [Savings] 0.10 - - This example causes 10% of the matching account's total to be -deferred to the `Savings' account--as a balanced virtual transaction, -which may be excluded from reports by using `--real'. - - -File: ledger.info, Node: Using Emacs to Keep Your Ledger, Next: Using GnuCash to Keep Your Ledger, Prev: Automated transactions, Up: Keeping a ledger - -3.11 Using Emacs to Keep Your Ledger -==================================== - -In the Ledger tarball is an Emacs module, `ledger.el'. This module -makes the process of keeping a text ledger much easier for Emacs users. -I recommend putting this at the top of your ledger file: - - ; -*-ledger-*- - - And this in your `.emacs' file, after copying `ledger.el' to your -`site-lisp' directory: - - (load "ledger") - - Now when you edit your ledger file, it will be in `ledger-mode'. -`ledger-mode' adds these commands: - -*C-c C-a* - For quickly adding new entries based on the form of older ones (see - previous section). - -*C-c C-c* - Toggles the "cleared" flag of the transaction under point. - -*C-c C-d* - Delete the entry under point. - -*C-c C-r* - Reconciles an account by displaying the transactions in another - buffer, where simply hitting the spacebar will toggle the pending - flag of the transaction in the ledger. Once all the appropriate - transactions have been marked, press C-c C-c in the reconcile - buffer to "commit" the reconciliation, which will mark all of the - entries as cleared, and display the new cleared balance in the - minibuffer. - -*C-c C-m* - Set the default month for new entries added with C-c C-a. This is - handy if you have a large number of transactions to enter from a - previous month. - -*C-c C-y* - Set the default year for new entries added with C-c C-a. This is - handy if you have a large number of transactions to enter from a - previous year. - - Once you enter the reconcile buffer, there are several key commands -available: - -*RET* - Visit the ledger file entry corresponding to the reconcile entry. - -*C-c C-c* - Commit the reconcialation. This marks all of the marked - transactions as "cleared", saves the ledger file, and then - displays the new cleared balance. - -*C-l* - Refresh the reconcile buffer by re-reading transactions from the - ledger data file. - -*SPC* - Toggle the transaction under point as cleared. - -*a* - Add a new entry to the ledger data file, and refresh the reconcile - buffer to include its transactions (if the entry is added to the - same account as the one being reconciled). - -*d* - Delete the entry related to the transaction under point. Note: - This may result in multiple transactions being deleted. - -*n* - Move to the next line. - -*p* - Move to the previous line. - -*C-c C-r* - -*r* - Attempt to auto-reconcile the transactions to the entered balance. - If it can do so, it will mark all those transactions as pending - that would yield the specified balance. - -*C-x C-s* - -*s* - Save the ledger data file, and show the current cleared balance for - the account being reconciled. - -*q* - Quit the reconcile buffer. - - There is also an `emacs' command which can be used to output reports -in a format directly `read'-able from Emacs Lisp. - - -File: ledger.info, Node: Using GnuCash to Keep Your Ledger, Next: Using timeclock to record billable time, Prev: Using Emacs to Keep Your Ledger, Up: Keeping a ledger - -3.12 Using GnuCash to Keep Your Ledger -====================================== - -The Ledger tool is fast and simple, but it offers no custom method for -actually editing the ledger. It assumes you know how to use a text -editor, and like doing so. Perhaps an Emacs mode will appear someday -soon to make editing Ledger's data files much easier. - - Until then, you are free to use GnuCash to maintain your ledger, and -the Ledger program for querying and reporting on the contents of that -ledger. It takes a little longer to parse the XML data format that -GnuCash uses, but the end result is identical. - - Then again, why would anyone use a Gnome-centric, 35 megabyte -behemoth to edit their data, and a one megabyte binary to query it? - - -File: ledger.info, Node: Using timeclock to record billable time, Prev: Using GnuCash to Keep Your Ledger, Up: Keeping a ledger - -3.13 Using timeclock to record billable time -============================================ - -The timeclock tool makes it easy to track time events, like clocking -into and out of a particular job. These events accumulate in a timelog -file. - - Each in/out event may have an optional description. If the "in" -description is a ledger account name, these in/out pairs may be viewed -as virtual transactions, adding time commodities (hours) to that -account. - - For example, the command-line version of the timeclock tool could be -used to begin a timelog file like: - - export TIMELOG=$HOME/.timelog - ti ClientOne category - sleep 10 - to waited for ten seconds - - The `.timelog' file now contains: - - i 2004/10/06 15:21:00 ClientOne category - o 2004/10/06 15:21:10 waited for ten seconds - - Ledger parses this directly, as if it had seen the following entry: - - 2004/10/06 category - (ClientOne) 10s - - In other words, the timelog event pair is seen as adding 0.00277h -(ten seconds) worth of time to the `ClientOne' account. This would be -considered billable time, which later could be invoiced and credited to -accounts receivable: - - 2004/11/01 (INV#1) ClientOne, Inc. - Receivable:ClientOne $0.10 - ClientOne -0.00277h @ $35.00 - - The above transaction converts the clocked time into an invoice for -the time spent, at an hourly rate of $35. Once the invoice is paid, -the money is deposited from the receivable account into a checking -account: - - 2004/12/01 ClientOne, Inc. - Assets:Checking $0.10 - Receivable:ClientOne - - And now the time spent has been turned into hard cash in the checking -account. - - The advantage to using timeclock and invoicing to bill time is that -you will always know, by looking at the balance report, exactly how -much unbilled and unpaid time you've spent working for any particular -client. - - I like to `!include' my timelog at the top of my company's -accounting ledger, with the attached prefix `Billable': - - ; -*-ledger-*- - - ; This is the ledger file for my company. But first, include the - ; timelog data, entering all of the time events within the umbrella - ; account "Billable". - - !account Billable - !include /home/johnw/.timelog - !end - - ; Here follows this fiscal year's transactions for the company. - - 2004/11/01 (INV#1) ClientOne, Inc. - Receivable:ClientOne $0.10 - Billable:ClientOne -0.00277h @ $35.00 - - 2004/12/01 ClientOne, Inc. - Assets:Checking $0.10 - Receivable:ClientOne - - -File: ledger.info, Node: Using XML, Prev: Keeping a ledger, Up: Top - -4 Using XML -*********** - -By default, Ledger uses a human-readable data format, and displays its -reports in a manner meant to be read on screen. For the purpose of -writing tools which use Ledger, however, it is possible to read and -display data using XML. This chapter documents that format. - - The general format used for Ledger data is: - - - - ... - ... - ...... - - - The data stream is enclosed in a `ledger' tag, which contains a -series of one or more entries. Each `entry' describes the entry and -contains a series of one or more transactions: - - - 2004/03/01 - - 100 - John Wiegley - - ... - ... - ...... - - - - The date format for `en:date' is always `YYYY/MM/DD'. The -`en:cleared' tag is optional, and indicates whether the transaction has -been cleared or not. There is also an `en:pending' tag, for marking -pending transactions. The `en:code' and `en:payee' tags both contain -whatever text the user wishes. - - After the initial entry data, there must follow a set of transactions -marked with `en:transactions'. Typically these transactions will all -balance each other, but if not they will be automatically balanced into -an account named `'. - - Within the `en:transactions' tag is a series of one or more -`transaction''s, which have the following form: - - - Expenses:Computer:Hardware - - - - $ - 90.00 - - - - - - This is a basic transaction. It may also be begin with `tr:virtual' -and/or `tr:generated' tags, to indicate virtual and auto-generated -transactions. Then follows the `tr:account' tag, which contains the -full name of the account the transaction is related to. Colons -separate parent from child in an account name. - - Lastly follows the amount of the transaction, indicated by -`tr:amount'. Within this tag is a `value' tag, of which there are four -different kinds, each with its own format: - - 1. boolean - - 2. integer - - 3. amount - - 4. balance - - The format of a boolean value is `true' or `false' surrounded by a -`boolean' tag, for example: - - true - - The format of an integer value is the numerical value surrounded by -an `integer' tag, for example: - - 12036 - - The format of an amount contains two members, the commodity and the -quantity. The commodity can have a set of flags that indicate how to -display it. The meaning of the flags (all of which are optional) are: - -*P* - The commodity is prefixed to the value. - -*S* - The commodity is separated from the value by a space. - -*T* - Thousands markers are used to display the amount. - -*E* - The format of the amount is European, with period used as a - thousands marker, and comma used as the decimal point. - - The actual quantity for an amount is an integer of arbitrary size. -Ledger uses the GNU multi-precision math library to handle such values. -The XML format assumes the reader to be equally capable. Here is an -example amount: - - - - $ - 90.00 - - - - Lastly, a balance value contains a series of amounts, each with a -different commodity. Unlike the name, such a value does need to -balance. It is called a balance because it sums several amounts. For -example: - - - - - $ - 90.00 - - - DM - 200.00 - - - - - That is the extent of the XML data format used by Ledger. It will -output such data if the `xml' command is used, and can read the same -data as long as the `expat' library was available when Ledger was built. - - - -Tag Table: -Node: Top1752 -Node: Introduction3430 -Ref: Introduction-Footnote-19370 -Node: Building the program9447 -Node: Getting help9994 -Node: Running Ledger10404 -Node: Usage overview11924 -Ref: Usage overview-Footnote-145394 -Ref: Usage overview-Footnote-245512 -Node: Commands45617 -Node: Options50846 -Node: Basic options51707 -Node: Report filtering53892 -Node: Output customization57772 -Node: Commodity reporting62677 -Node: Environment variables64775 -Node: Format strings65423 -Node: Value expressions70795 -Node: Period expressions77126 -Node: File format78714 -Node: Some typical queries83582 -Node: Budgeting and forecasting87304 -Node: Keeping a ledger90044 -Node: Stating where money goes92753 -Node: Assets and Liabilities95406 -Node: Commodities and Currencies103551 -Node: Accounts and Inventories109712 -Node: Understanding Equity111307 -Node: Dealing with Petty Cash113214 -Node: Working with multiple funds and accounts114311 -Node: Archiving previous years119027 -Node: Virtual transactions121551 -Node: Automated transactions123227 -Node: Using Emacs to Keep Your Ledger126247 -Node: Using GnuCash to Keep Your Ledger129318 -Node: Using timeclock to record billable time130227 -Node: Using XML132987 - -End Tag Table diff --git a/ledger.pdf b/ledger.pdf deleted file mode 100644 index c94d2f88..00000000 Binary files a/ledger.pdf and /dev/null differ diff --git a/ledger.texi b/ledger.texi deleted file mode 100644 index bd42a3b0..00000000 --- a/ledger.texi +++ /dev/null @@ -1,3960 +0,0 @@ -\input texinfo @c -*-texinfo-*- - -@setfilename ledger.info -@settitle Ledger: Command-Line Accounting - -@dircategory User Applications -@copying -Copyright (c) 2003-2006, John Wiegley. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of New Artisans LLC nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -@end copying - -@documentencoding iso-8859-1 - -@iftex -@finalout -@end iftex - -@titlepage -@title Ledger: Command-Line Accounting -@author John Wiegley -@end titlepage - -@direntry -* Ledger: (ledger). Command Line Accounting -@end direntry - -@contents - -@ifnottex -@node Top, Introduction, (dir), (dir) -@top Overview - -@insertcopying -@end ifnottex - -@menu -* Introduction:: -* Running Ledger:: -* Keeping a ledger:: -* Using XML:: -@end menu - -@node Introduction, Running Ledger, Top, Top -@chapter Introduction - -Ledger is an accounting tool with the moxie to exist. It provides no -bells or whistles, and returns the user to the days before user -interfaces were even a twinkling in their father's CRT. - -What it does offer is a double-entry accounting ledger with all the -flexibility and muscle of its modern day cousins, without any of the -fat. Think of it as the Bran Muffin of accounting tools. - -To use it, you need to start keeping a ledger. This is the basis of -all accounting, and if you haven't started yet, now is the time to -learn. The little booklet that comes with your checkbook is a ledger, -so we'll describe double-entry accounting in terms of that. - -A checkbook ledger records debits (subtractions, or withdrawals) and -credits (additions, or deposits) with reference to a single account: -the checking account. Where the money comes from, and where it goes -to, are described in the payee field, where you write the person or -company's name. The ultimate aim of keeping a checkbook ledger is to -know how much money is available to spend. That's really the aim of -all ledgers. - -What computers add is the ability to walk through these transactions, -and tell you things about your spending habits; to let you devise -budgets and get control over your spending; to squirrel away money -into virtual savings account without having to physically move money -around; etc. As you keep your ledger, you are recording information -about your life and habits, and sometimes that information can start -telling you things you aren't aware of. Such is the aim of all good -accounting tools. - -The next step up from a checkbook ledger, is a ledger that keeps track -of all your accounts, not just checking. In such a ledger, you record -not only who gets paid---in the case of a debit---but where the money -came from. In a checkbook ledger, its assumed that all the money -comes from your checking account. But in a general ledger, you write -transaction two-lines: the source account and target account. -@emph{There must always be a debit from at least one account for every -credit made to another account}. This is what is meant by -``double-entry'' accounting: the ledger must always balance to zero, -with an equal number of debits and credits. - -For example, let's say you have a checking account and a brokerage -account, and you can write checks from both of them. Rather than keep -two checkbooks, you decide to use one ledger for both. In this -general ledger you need to record a payment to Pacific Bell for your -monthly phone bill. The cost is $23.00, let's say, and you want to -pay it from your checking account. In the general ledger you need to -say where the money came from, in addition to where it's going to. -The entry might look like this: - -@smallexample -9/29 BAL Pacific Bell $-200.00 $-200.00 - Equity:Opening Balances $200.00 -9/29 BAL Checking $100.00 $100.00 - Equity:Opening Balances $-100.00 -9/29 100 Pacific Bell $23.00 $223.00 - Checking $-23.00 $77.00 -@end smallexample - -The first line shows a payment to Pacific Bell for $23.00. Because -there is no ``balance'' in a general ledger---it's always zero---we -write in the total balance of all payments to ``Pacific Bell'', which -now is $223.00 (previously the balance was $200.00). This is done by -looking at the last entry for ``Pacific Bell'' in the ledger, adding -$23.00 to that amount, and writing the total in the balance column. -And the money came from ``Checking''---a withdrawal of $23.00---which -leaves the ending balance in ``Checking'' at $77.00. This is a very -manual procedure; but that's where computers come in... - -The transaction must balance to $0: $23 went to Pacific Bell, $23 came -from Checking. There is nothing left over to be accounted for, since -the money has simply moved from one account to another. This is the -basis of double-entry accounting: that money never pops in or out of -existence; it is always a transaction from one account to another. - -Keeping a general ledger is the same as keeping two separate ledgers: -One for Pacific Bell and one for Checking. In that case, each time a -payment is written into one, you write a corresponding withdrawal into -the other. This makes it easier to write in a ``running balance'', -since you don't have to look back at the last time the account was -referenced---but it also means having a lot of ledger books, if you -deal with multiple accounts. - -Enter the beauty of computerized accounting. The purpose of the -Ledger program is to make general ledger accounting simple, by keeping -track of the balances for you. Your only job is to enter the -transactions. If a transaction does not balance, Ledger displays an -error and indicates the incorrect transaction.@footnote{In some -special cases, it automatically balances this entry for you.} - -In summary, there are two aspects of Ledger use: updating the ledger -data file, and using the Ledger tool to view the summarized result of -your entries. - -And just for the sake of example---as a starting point for those who -want to dive in head-first---here are the ledger entries from above, -formatting as the ledger program wishes to see them: - -@smallexample -2004/09/29 Pacific Bell - Payable:Pacific Bell $-200.00 - Equity:Opening Balances - -2004/09/29 Checking - Accounts:Checking $100.00 - Equity:Opening Balances - -2004/09/29 Pacific Bell - Payable:Pacific Bell $23.00 - Accounts:Checking -@end smallexample - -The account balances and registers in this file, if saved as -@file{ledger.dat}, could be reported using: - -@example -$ ledger -f ledger.dat balance -$ ledger -f ledger.dat register checking -$ ledger -f ledger.dat register bell -@end example - -@menu -* Building the program:: -* Getting help:: -@end menu - -@node Building the program, Getting help, Introduction, Introduction -@section Building the program - -Ledger is written in ANSI C++, and should compile on any platform. It -depends on the GNU multiprecision integer library (libgmp), and the -Perl regular expression library (libpcre). It was developed using GNU -make and gcc 3.3, on a PowerBook running OS/X. - -To build and install once you have these libraries on your system, -enter these commands: - -@example -./configure && make install -@end example - -@node Getting help, , Building the program, Introduction -@section Getting help - -If you need help on how to use Ledger, or run into problems, you can -just the Ledger mailing list at the following Web address: - -@example -https://lists.sourceforge.net/lists/listinfo/ledger-discuss -@end example - -You can also find help at the @samp{#ledger} channel on the IRC server -@samp{irc.freenode.net}. - -@node Running Ledger, Keeping a ledger, Introduction, Top -@chapter Running Ledger - -Ledger has a very simple command-line interface, named---enticing -enough---@command{ledger}. It supports a few reporting commands, and -a large number of options for refining the output from those commands. -The basic syntax of any ledger command is: - -@example -ledger [OPTIONS...] COMMAND [ARGS...] -@end example - -Command options must always precede the command word. After the -command word there may appear any number of arguments. For most -commands, these arguments are regular expressions that cause the -output to relate only to transactions matching those regular -expressions. For the @command{entry} command, the arguments have a -special meaning, described below. - -The regular expressions arguments always match the account name that a -transaction refers to. To match on the payee of the entry instead, -precede the regular expression with @samp{--}. For example, the -following balance command reports account totals for rent, food and -movies, but only those whose payee matches Freddie: - -@example -ledger bal rent food movies -- freddie -@end example - -There are many, many command options available with the -@command{ledger} command, and it takes a while to master them. -However, none of them are required to use the basic reporting -commands. - -@menu -* Usage overview:: -* Commands:: -* Options:: -* Format strings:: -* Value expressions:: -* Period expressions:: -* File format:: -* Some typical queries:: -* Budgeting and forecasting:: -@end menu - -@node Usage overview, Commands, Running Ledger, Running Ledger -@section Usage overview - -Before getting into the details of how to run Ledger, it will be -easier to introduce the features in the context of their typical -usage. To that end, this section presents a series of recipes, -gradually introducing all of the command-line features of Ledger. - -For the purpose of these examples, assume the environment variable -@var{LEDGER} is set to the file @file{sample.dat} (which is included -in the distribution), and that the contents of that file are: - -@smallexample -= /^Expenses:Books/ - (Liabilities:Taxes) -0.10 - -~ Monthly - Assets:Bank:Checking $500.00 - Income:Salary - -2004/05/01 * Checking balance - Assets:Bank:Checking $1,000.00 - Equity:Opening Balances - -2004/05/01 * Investment balance - Assets:Brokerage 50 AAPL @ $30.00 - Equity:Opening Balances - -2004/05/14 * Pay day - Assets:Bank:Checking $500.00 - Income:Salary - -2004/05/27 Book Store - Expenses:Books $20.00 - Liabilities:MasterCard - -2004/05/27 (100) Credit card company - Liabilities:MasterCard $20.00 - Assets:Bank:Checking -@end smallexample - -This sample file demonstrates a basic principle of accounting which it -is recommended you follow: Keep all of your accounts under five parent -Assets, Liabilities, Income, Expenses and Equity. It is important to -do so in order to make sense out of the following examples. - -@subsection Checking balances - -Ledger has seven basic commands, but by far the most often used are -@command{balance} and @command{register}. To see a summary balance of -all accounts, use: - -@example -ledger bal -@end example - -@command{bal} is a short-hand for @command{balance}. This command -prints out the summary totals of the five parent accounts used in -@file{sample.dat}: - -@smallexample - $1,480.00 - 50 AAPL Assets - $-2,500.00 Equity - $20.00 Expenses - $-500.00 Income - $-2.00 Liabilities --------------------- - $-1,502.00 - 50 AAPL -@end smallexample - -None of the child accounts are shown, just the parent account totals. -We can see that in @samp{Assets} there is $1,480.00, and 50 shares of -Apple stock. There is also a negative grand total. Usually the grand -total is zero, which means that all accounts balance@footnote{It is -impossible for accounts not to balance in ledger; it reports an error -if a transaction does not balance}. In this case, since the 50 shares -of Apple stock cost $1,500.00 dollars, then these two amounts balance -each other in the grand total. The extra $2.00 comes from a virtual -transaction being added by the automatic entry at the top of the file. -The entry is virtual because the account name was surrounded by -parentheses in an automatic entry. Automatic entries will be -discussed later, but first let's remove the virtual transaction from -the balance report by using the @option{--real} option: - -@example -ledger --real bal -@end example - -Now the report is: - -@smallexample - $1,480.00 - 50 AAPL Assets - $-2,500.00 Equity - $20.00 Expenses - $-500.00 Income --------------------- - $-1,500.00 - 50 AAPL -@end smallexample - -Since the liability was a virtual transaction, it has dropped from the -report and we see that final total is balanced. - -But we only know that it balances because @file{sample.dat} is quite -simple, and we happen to know that the 50 shares of Apple stock cost -$1,500.00. We can verify that things really balance by reporting the -Apple shares in terms of their cost, instead of their quantity. To do -this requires the @option{--basis}, or @option{-B}, option: - -@example -ledger --real -B bal -@end example - -This command reports: - -@smallexample - $2,980.00 Assets - $-2,500.00 Equity - $20.00 Expenses - $-500.00 Income -@end smallexample - -With the basis cost option, the grand total has disappeared, as it is -now zero. The confirms that the cost of everything balances to zero, -@emph{which must always be true}. Reporting the real basis cost -should never yield a remainder@footnote{If it ever does, then -generated transactions are involved, which can be removed using -@option{--actual}}. - -@subsubsection Sub-account balances - -The totals reported by the balance command are only the topmost parent -accounts. To see the totals of all child accounts as well, use the -@option{-s} option: - -@example -ledger --real -B -s bal -@end example - -This reports: - -@smallexample - $2,980.00 Assets - $1,480.00 Bank:Checking - $1,500.00 Brokerage - $-2,500.00 Equity:Opening Balances - $20.00 Expenses:Books - $-500.00 Income:Salary -@end smallexample - -This shows that the @samp{Assets} total is made up from two child -account, but that the total for each of the other accounts comes from -one child account. - -Sometimes you may have a lot of children, nested very deeply, but only -want to report the first two levels. This can be done with a display -predicate, using a value expression. In the value expression, -@code{T} represents the reported total, and @code{l} is the display -level for the account: - -@example -ledger --real -B -d "T&l<=2" bal -@end example - -This reports: - -@smallexample - $2,980.00 Assets - $1,480.00 Bank - $1,500.00 Brokerage - $-2,500.00 Equity:Opening Balances - $20.00 Expenses:Books - $-500.00 Income:Salary -@end smallexample - -Instead of reporting @samp{Bank:Checking} as a child of @samp{Assets}, -it report only @samp{Bank}, since that account is a nesting level of -2, while @samp{Checking} is at level 3. - -To review the display predicate used---@code{T&l<=2}---this rather -terse expression means: Display an account only if it has a non-zero -total (@code{T}), and its nesting level is less than or equal to 2 -(@code{l<=2}). - -@subsubsection Specific account balances - -While reporting the totals for all accounts can be useful, most often -you will want to check the balance of a specific account or accounts. -To do this, put one or more account names after the balance command. -Since these names are really regular expressions, you can use partial -names if you wish: - -@example -ledger bal checking -@end example - -Reports: - -@smallexample - $1,480.00 Assets:Bank:Checking -@end smallexample - -Any number of names may be used: - -@example -ledger bal checking broker liab -@end example - -Reports: - -@smallexample - $1,480.00 Assets:Bank:Checking - 50 AAPL Assets:Brokerage - $-2.00 Liabilities -@end smallexample - -In this case no grand total is reported, because you are asking for -specific account balances. - -For those comfortable with regular expressions, any Perl regexp is -allowed: - -@example -ledger bal ^assets.*checking ^liab -@end example - -Reports: - -@smallexample - $1,480.00 Assets:Bank:Checking - $-2.00 Liabilities:Taxes -@end smallexample - -@subsection The register report - -While the @command{balance} command can be very handy for checking -account totals, by far the most powerful of Ledger's reporting tools -is the @command{register} command. In fact, internally both commands -use the same logic, but report the results differently: -@command{balance} shows the summary totals, while @command{register} -reports each transaction and how it contributes to that total. - -Paradoxically, the most basic form of @command{register} is almost -never used, since it displays every transaction: - -@example -ledger reg -@end example - -@command{reg} is a short-hand for @command{register}. This command -reports: - -@smallexample -2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 - Equity:Opening Balan.. $-1,000.00 0 -2004/05/01 Investment balance Assets:Brokerage 50 AAPL 50 AAPL - Equity:Opening Balan.. $-1,500.00 $-1,500.00 - 50 AAPL -2004/05/14 Pay day Assets:Bank:Checking $500.00 $-1,000.00 - 50 AAPL - Income:Salary $-500.00 $-1,500.00 - 50 AAPL -2004/05/27 Book Store Expenses:Books $20.00 $-1,480.00 - 50 AAPL - Liabilities:MasterCard $-20.00 $-1,500.00 - 50 AAPL - (Liabilities:Taxes) $-2.00 $-1,502.00 - 50 AAPL -2004/05/27 Credit card company Liabilities:MasterCard $20.00 $-1,482.00 - 50 AAPL - Assets:Bank:Checking $-20.00 $-1,502.00 - 50 AAPL -@end smallexample - -This rather verbose output shows every account transaction in -@file{sample.dat}, and how it affects the running total. The final -total is identical to what we saw with the plain @command{balance} -command. To see how things really balance, we can use @samp{--real --B}, just as we did with @command{balance}: - -@example -ledger --real -B reg -@end example - -Reports: - -@smallexample -2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 - Equity:Opening Balan.. $-1,000.00 0 -2004/05/01 Investment balance Assets:Brokerage $1,500.00 $1,500.00 - Equity:Opening Balan.. $-1,500.00 0 -2004/05/14 Pay day Assets:Bank:Checking $500.00 $500.00 - Income:Salary $-500.00 0 -2004/05/27 Book Store Expenses:Books $20.00 $20.00 - Liabilities:MasterCard $-20.00 0 -2004/05/27 Credit card company Liabilities:MasterCard $20.00 $20.00 - Assets:Bank:Checking $-20.00 0 -@end smallexample - -Here we see that everything balances to zero in the end, as it must. - -@subsubsection Specific register queries - -The most common use of the register command is to summarize -transactions based on the account(s) they affect. Using -@file{sample.dat} as as example, we could look at all book purchases -using: - -@example -ledger reg books -@end example - -Reports: - -@smallexample -2004/05/29 Book Store Expenses:Books $20.00 $20.00 -@end smallexample - -If a double-dash (@samp{--}) occurs in the list of regular -expressions, any following arguments are matched against payee names, -instead of account names: - -@example -ledger reg ^liab -- credit -@end example - -Reports: - -@smallexample -2004/05/29 Credit card company Liabilities:MasterCard $20.00 $20.00 -@end smallexample - -There are many reporting options for tailoring which transactions are -found, and also how to summarize the various amounts and totals that -result. These are plumbed in greater depth below. - -@subsection Selecting transactions - -Although the easiest way to use the register is to report all the -transactions affecting a set of accounts, it can often result in more -information than you want. To cope with an ever-growing amount of -data, there are several options which can help you pinpoint your -report to exactly the transactions that interest you most. This is -called the ``calculation'' phase of Ledger. All of its related -options are documented under @option{--help-calc}. - -@subsubsection By date - -@c -c, --current show only current and past entries (not future) - -@option{--current}(@option{-c}) displays entries occurring on or -before the current date. Any entry recorded for a future date will be -ignored, as if it had not been seen. This is useful if you happen to -pre-record entries, but still wish to view your balances in terms of -what is available today. - -@c -b, --begin DATE set report begin date -@c -e, --end DATE set report end date - -@option{--begin DATE} (@option{-b DATE}) limits the report to only -those entries occurring on or after @var{DATE}. The running total in -the register will start at zero with the first transaction, even if -there are earlier entries. - -To limit the display only, but still add earlier transactions to the -running total, use the display expression @samp{-d 'd>=[DATE]'}): - -@example -ledger --basis -b may -d 'd>=[5/14]' reg ^assets -@end example - -Reports: - -@smallexample -2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 -2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 -@end smallexample - -In this example, the displayed transactions start from @samp{5/14}, -but the calculated total starts from the beginning of @samp{may}. - -@option{--end DATE} (@option{-e DATE}) states when reporting should -end, both calculation and display. The ending date is inclusive. - -The @var{DATE} argument to the @option{-b} and @option{-e} options can -be rather flexible. Assuming the current date to be November 15, -2004, then all of the following are equivalent: - -@example -ledger -b oct bal -ledger -b "this oct" bal -ledger -b 2004/10 bal -ledger -b 10 bal -ledger -b last bal -ledger -b "last month" bal -@end example - -@c -p, --period STR report using the given period -@c --period-sort EXPR sort each report period's entries by EXPR - -To constrain the report to a specific time period, use -@option{--period} (@option{-p}). A time period may have both a -beginning and an end, or neither, as well as a specified interval. -Here are a few examples: - -@example -ledger -p 2004 bal -ledger -p august bal -ledger -p "from aug to oct" bal -ledger -p "daily from 8/1 to 8/15" bal -ledger -p "weekly since august" bal -ledger -p "monthly from feb to oct" bal -ledger -p "quarterly in 2004" bal -ledger -p yearly bal -@end example - -See @ref{Period expressions} for more on syntax. Also, all of the -options @option{-b}, @option{-e} and @option{-p} may be used together, -but whatever information occurs last takes priority. An example of -such usage (in a script, perhaps) would be: - -@example -ledger -b 2004 -e 2005 -p monthly reg ^expenses -@end example - -This command is identical to: - -@example -ledger -p "monthly in 2004" reg ^expenses -@end example - -The transactions within a period may be sorted using -@option{--period-sort}, which takes a value expression. This is -similar to the @option{--sort} option, except that it sorts within -each period entry, rather than sorting all transactions in the report. -See the documentation on @option{--sort} below for more details. - -@subsubsection By status - -By default, all regular transactions are included in each report. To -limit the report to certain kinds of transactions, use one or more of -the following options: - -@table @option -@item -C, --cleared -Consider only cleared transactions. -@item -U, --uncleared -Consider only uncleared and pending transactions. -@item -R, --real -Consider only real (non-virtual) transactions. -@item -L, --actual -Consider only actual (non-automated) transactions. -@end table - -Cleared transactions are indicated by an asterix placed just before -the payee name in a transaction. The meaning of this flag is up to -the user, but typically it means that an entry has been seen on a -financial statement. Pending transactions use an exclamation mark in -the same position, but are mainly used only by reconciling software. -Uncleared transactions are for things like uncashed checks, credit -charges that haven't appeared on a statement yet, etc. - -Real transactions are all non-virtual transactions, where the account -name is not surrounded by parentheses or square brackets. Virtual -transactions are useful for showing a transfer of money that never -really happened, like money set aside for savings without actually -transferring it from the parent account. - -Actual transactions are those not generated, either as part of an -automated entry, or a budget or forecast report. A useful of when you -might like to filter out generated transactions is with a budget: - -@example -ledger --budget --actual reg ^expenses -@end example - -This command outputs all transactions affecting a budgeted account, -but without subtracting the budget amount (because the generated -transactions are suppressed with @option{--actual}). The report shows -how much you actually spent on budgeted items. - -@subsubsection By relationship - -@c -r, --related calculate report using related transactions - -Normally, a register report includes only the transactions that match -the regular expressions specified after the command word. For -example, to report all expenses: - -@example -ledger reg ^expenses -@end example - -This reports: - -@smallexample -2004/05/29 Book Store Expenses:Books $20.00 $20.00 -@end smallexample - -Using @option{--related} (@option{-r}) reports the transactions that -did not match your query, but only in entries that otherwise would -have matched. This has the effect of indicating where money came -from, or when to: - -@example -ledger -r reg ^expenses -@end example - -Reports: - -@smallexample -2004/05/29 Book Store Liabilities:MasterCard $20.00 $20.00 -@end smallexample - -@subsubsection By budget - -@c --budget generate budget entries based on FILE - -There is more information about budgeting and forecasting in -@ref{Budgeting and forecasting}. Basically, if you have any period -entries in your ledger file, you can use these options. A period -entry looks like: - -@example -~ Monthly - Assets:Bank:Checking $500.00 - Income:Salary -@end example - -The difference from a regular entry is that the first line begins with -a tilde (~), and instead of a payee there's a period expression -(@ref{Period expressions}). Otherwise, a period entry is in every -other way the same as a regular entry. - -With such an entry in your ledger file, the @option{--budget} option -will report only transactions that match a budgeted account. Using -@file{sample.dat} from above: - -@example -ledger --budget reg ^income -@end example - -Reports: - -@smallexample -2004/05/01 Budget entry Income:Salary $500.00 $500.00 -2004/05/14 Pay day Income:Salary $-500.00 0 -@end smallexample - -The final total is zero, indicating that the budget matched exactly -for the reported period. Budgeting is most often helpful with period -reporting; for example, to show monthly budget results use -@option{--budget -p monthly}. - -@c --add-budget show all transactions plus the budget -@c --unbudgeted show only unbudgeted transactions - -The @option{--add-budget} option reports all matching transactions in -addition to budget transactions; while @option{--unbudgeted} shows -only those that don't match a budgeted account. To summarize: - -@table @option -@item --budget -Show transactions matching budgeted accounts. -@item --unbudgeted -Show transactions matching unbudgeted accounts. -@item --add-budget -Show both budgeted and unbudgeted transactions together (i.e., add the -generated budget transactions to the regular report). -@end table - -@c --forecast EXPR generate forecast entries while EXPR is true - -A report with the @option{--forecast} option will add budgeted -transactions while the specified value expression is true. For -example: - -@example -ledger --forecast 'd<[2005] reg ^income -@end example - -Reports: - -@smallexample -2004/05/14 Pay day Income:Salary $-500.00 $-500.00 -2004/12/01 Forecast entry Income:Salary $-500.00 $-1,000.00 -2005/01/01 Forecast entry Income:Salary $-500.00 $-1,500.00 -@end smallexample - -The date this report was made was November 5, 2004; the reason the -first forecast entry is in december is that forecast entries are only -added for the future, and they only stop after the value expression -has matched at least once, which is why the January entry appears. A -forecast report can be very useful for determining when money will run -out in an account, or for projecting future cash flow: - -@example -ledger --forecast 'd<[2008]' -p yearly reg ^inc ^exp -@end example - -This reports balances projected income against projected expenses, -showing the resulting total in yearly intervals until 2008. For the -case of @file{sample.dat}, which has no budgeted expenses, the result -of the above command (in November 2004) is: - -@smallexample -2004/01/01 - 2004/12/31 Income:Salary $-1,000.00 $-1,000.00 - Expenses:Books $20.00 $-980.00 -2005/01/01 - 2005/12/31 Income:Salary $-6,000.00 $-6,980.00 -2006/01/01 - 2006/12/31 Income:Salary $-6,000.00 $-12,980.00 -2007/01/01 - 2007/12/31 Income:Salary $-6,000.00 $-18,980.00 -2008/01/01 - 2008/01/01 Income:Salary $-500.00 $-19,480.00 -@end smallexample - -@subsubsection By value expression - -@c -l, --limit EXPR calculate only transactions matching EXPR - -Value expressions can be quite complex, and are treated more fully in -@ref{Value expressions}. They can be used for limiting a report with -@option{--limit} (@option{-l}). The following command report income -since august, but expenses since october: - -@example -ledger -l '(/income/&d>=[aug])|(/expenses/&d>=[oct])' reg -@end example - -The basic form of this value expression is @samp{(A&B)|(A&B)}. The -@samp{A} in each part matches against an account name with -@samp{/name/}, while each @samp{B} part compares the date of the -transaction (@samp{d}) with a specified month. The resulting report -will contain only transactions which match the value expression. - -@c -t, --amount EXPR use EXPR to calculate the displayed amount -@c -T, --total EXPR use EXPR to calculate the displayed total - -Another use of value expressions is to calculate the amount reported -for each line of a register report, or for computing the subtotal of -each account shown in a balance report. This example divides each -transaction amount by two: - -@example -ledger -t 'a/2' reg ^exp -@end example - -The @option{-t} option doesn't affect the running total, only how the -transaction amount is displayed. To change the running total, use -@option{-T}. In that case, you will likely want to use the total -(@samp{O}) instead of the amount (@samp{a}): - -@example -ledger -T 'O/2' reg ^exp -@end example - -@subsection Massaging register output - -Even after filtering down your data to just the transactions you're -interested in, the default reporting method of one transaction per -line is often still too much. To combat this complexity, it is -possible to ask Ledger to report the details to you in many different -forms, summarized in various ways. This is the ``display'' phase of -Ledger, and is documented under @option{--help-disp}. - -@subsubsection Summarizing - -@c -n, --collapse register: collapse entries with multiple transactions - -When multiple transactions relate to a single entry, they are reported -as part of that entry. For example, in the case of @file{sample.dat}: - -@example -ledger reg -- book -@end example - -Reports: - -@smallexample -2004/05/29 Book Store Expenses:Books $20.00 $20.00 - Liabilities:MasterCard $-20.00 0 - (Liabilities:Taxes) $-2.00 $-2.00 -@end smallexample - -All three transactions are part of one entry, and as such the entry -details are printed only once. To report every entry on a single -line, use @option{-n} to collapse entries with multiple transactions: - -@example -ledger -n reg -- book -@end example - -Reports: - -@smallexample -2004/05/29 Book Store $-2.00 $-2.00 -@end smallexample - -In the balance report, @option{-n} causes the grand total not to be -displayed at the bottom of the report. - -@c -s, --subtotal balance: show sub-accounts; other: show subtotals - -If an account occurs more than once in a report, it is possible to -combine them all and report the total per-account, using @option{-s}. -For example, this command: - -@example -ledger -B reg ^assets -@end example - -Reports: - -@smallexample -2004/05/01 Checking balance Assets:Bank:Checking $1,000.00 $1,000.00 -2004/05/01 Investment balance Assets:Brokerage $1,500.00 $2,500.00 -2004/05/14 Pay day Assets:Bank:Checking $500.00 $3,000.00 -2004/05/27 Credit card company Assets:Bank:Checking $-20.00 $2,980.00 -@end smallexample - -But if the @option{-s} option is added, the result becomes: - -@smallexample -2004/05/01 - 2004/05/29 Assets:Bank:Checking $1,480.00 $1,480.00 - Assets:Brokerage $1,500.00 $2,980.00 -@end smallexample - -When account subtotaling is used, only one entry is printed, and the -date and name reflect the range of the combined transactions. - -@c -P, --by-payee show summarized totals by payee - -With @option{-P}, transactions relating to the same payee are -combined. In this case, the date of the combined entry is that of the -latest transaction. - -@c -x, --comm-as-payee set commodity name as the payee, for reporting - -@option{-x} changes the payee name for each transaction to be the same -as the commodity it uses. This can be especially useful combined with -other options, like @option{-P}. For example: - -@example -ledger -Px reg ^assets -@end example - -Reports: - -@smallexample -2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 -2004/05/01 AAPL Assets:Brokerage 50 AAPL $1,480.00 - 50 AAPL -@end smallexample - -This reports shows the subtotal for each commodity held, and where it -is located. To see the basis cost, or initial investment, add -@option{-B}. Applied to the example above: - -@smallexample -2004/05/29 $ Assets:Bank:Checking $1,480.00 $1,480.00 -2004/05/01 AAPL Assets:Brokerage $1,500.00 $2,980.00 -@end smallexample - -@c -E, --empty balance: show accounts with zero balance - -The only other options which affect summarized totals is @option{-E}, -which works only in the balance report. In this case, it shows -matching accounts with a zero a balance, which are ordinarily -excluded. This can be useful to see all the accounts involved in a -report, even if some have no total. - -@subsubsection Quick periods - -Although the @option{-p} option (also @option{--period}) is much more -versatile, there are other options to make the most common period -reports easier: - -@table @option -@item -W, --weekly -Show weekly sub-totals. Same as @samp{-p weekly}. -@item -M, --monthly -Show monthly sub-totals. Same as @samp{-p monthly}. -@item -Y, --yearly -Show yearly sub-totals. Same as @samp{-p yearly}. -@end table - -@c --dow show a days-of-the-week report - -There is one kind of period report cannot be done with @option{-p}. -This is the @option{--dow}, or ``days of the week'' report, which -shows summarized totals for each day of the week. The following -examples shows a ``day of the week'' report of income and expenses: - -@example -ledger --dow reg ^inc ^exp -@end example - -Reports: - -@smallexample -2004/05/27 Thursdays Expenses:Books $20.00 $20.00 -2004/05/14 Fridays Income:Salary $-500.00 $-480.00 -@end smallexample - -@subsubsection Ordering and width - -@c -S, --sort EXPR sort report according to the value expression EXPR - -The transactions displayed in a report are shown in the same order as -they appear in the ledger file. To change the order and sort a -report, use the @option{--sort} option. @option{--sort} takes a value -expression to determine the value to sort against, making it possible -to sort according to complex criteria. Here are some simple and -useful examples: - -@example -ledger --sort d reg ^exp # sort by date -ledger --sort t reg ^exp # sort by amount total -ledger --sort -t reg ^exp # reverse sort by amount total -ledger --sort Ut reg ^exp # sort by abs amount total -@end example - -For the balance report, you will want to use @samp{T} instead of -@samp{t}: - -@example -ledger --sort T reg ^exp # sort by amount total -ledger --sort -T reg ^exp # reverse sort by amount total -ledger --sort UT reg ^exp # sort by abs amount total -@end example - -The @option{--sort} options sorts all transactions in a report. If -periods are used (such as @option{--monthly}), this can get somewhat -confusing. In that case, you'll probably want to sort within periods -using @option{--period-sort} instead of @option{--sort}. - -@c -w, --wide for the default register report, use 132 columns - -And if the register seems too cramped, and you have a lot of screen -real estate, you can use @option{-w} to format the report within 132 -acolumns, instead of 80. You are more likely then to see full payee -and account names, as well as properly formatted totals when -long-named commodities are used. - -If you want only the first or last N entries to be printed---which can -be very useful for viewing the last 10 entries in your checking -account, while also showing the cumulative balance from all -entries---use the @option{--head} and/or @option{--tail} options. The -two options may be used simultaneously, for example: - -@example -ledger --tail 20 reg checking -@end example - -If the output from your command is very long, Ledger can output the -data to a pager utility, such as @command{more} or @command{less}: - -@example -ledger --pager /usr/bin/less reg checking -@end example - -@subsubsection Averages and percentages - -@c -A, --average report average transaction amount - -To see the running total changed to a running average, use -@option{-A}. The final transaction's total will be the overall -average of all displayed transactions. The works in conjunction with -period reporting, so that you can see your monthly average expenses -with: - -@example -ledger -AM reg ^expenses:food -ledger -AMn reg ^expenses -@end example - -This works in the balance report too: - -@example -ledger -AM bal ^expenses:food -ledger -AMs bal ^expenses -@end example - -@c -D, --deviation report deviation from the average - -The @option{-D} option changes the running average into a deviation -from the running average. This only makes sense in the register -report, however. - -@example -ledger -DM reg ^expenses:food -@end example - -@c -%, --percentage report balance totals as a percentile of the parent - -In the balance report only, @option{-%} changes the reported totals -into a percentage of the parent account. This kind of report is -confusing if negative amounts are involved, and doesn't work at all if -multiple commodities occur in an account's history. It has a somewhat -limited usefulness, therefore, but in certain cases it can be handy, -such as reviewing overall expenses: - -@example -ledger -%s -S T bal ^expenses -@end example - -@subsubsection Reporting total data - -@c --totals in the "xml" report, include running total - -Normally in the @command{xml} report, only transaction amounts are -printed. To include the running total under a @samp{} tag, use -@option{--totals}. This does not affect any other report. - -@c -j, --amount-data print only raw amount data (useful for scripting) -@c -J, --total-data print only raw total data - -In the register report only, the output can be changed with -@option{-j} to show only the date and the amount---without -commodities. This only makes sense if a single commodity appears in -the report, but can be quite useful for scripting, or passing the data -to Gnuplot. To show only the date and running total, use @option{-J}. - -@subsubsection Display by value expression - -@c -d, --display EXPR display only transactions matching EXPR - -With @option{-d} you can decide which transactions (or accounts in the -balance report) are displayed, according to a value expression. The -computed total is not affected, only the display. This can be very -useful for shortening a report without changing the running total: - -@example -ledger -d 'd>=[last month]' reg checking -@end example - -This command shows the checking account's register, beginning from -last month, but with the running total reflecting the entire history -of the account. - -@subsubsection Change report format - -@c -y, --date-format STR use STR as the date format (default: %Y/%m/%d) - -When dates are printed in any report, the default format is -@samp{%Y/%m/%d}, which yields dates of the form @samp{YYYY/mm/dd}. -This can be changed with @option{-y}, whose argument is a -@code{strftime} string---see your system's C library documentation for -the allowable codes. Mostly you will want to use @samp{%Y}, @samp{%m} -and @samp{%d}, in whatever combination is convenient for your locale. - -@c -F, --format STR use STR as the format; for each report type, use: -@c --balance-format --register-format --print-format -@c --plot-amount-format --plot-total-format --equity-format -@c --prices-format --wide-register-format - -To change the format of the entire reported line, use @option{-F}. It -supports quite a large number of options, which are all documented in -@ref{Format strings}. In addition, each specific kind of report -(except for @command{xml}) can be changed using one of the following -options: - -@table @option -@item --balance-format -@command{balance} report. Default: -@smallexample -%20T %2_%-a\n -@end smallexample - -@item --register-format -@command{register} report. Default: -@smallexample -%D %-.20P %-.22A %12.66t %12.80T\n%/%32|%-.22A %12.66t %12.80T\n -@end smallexample - -@item --print-format -@command{print} report. Default: -@smallexample -%D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n -@end smallexample - -@item --plot-amount-format -@command{register} report when @option{-j} (plot amount) is used. Default: -@smallexample -%D %(St)\n -@end smallexample - -@item --plot-total-format -@command{register} report when @option{-J} (plot total) is used. Default: -@smallexample -%D %(ST)\n -@end smallexample - -@item --equity-format -@command{equity} report. Default: -@smallexample -\n%D %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n -@end smallexample - -@item --prices-format -@command{prices} report. Default: -@smallexample -\n%D %Y%C%P\n%/ %-34W %12t\n -@end smallexample - -@item --wide-register-format -@command{register} report when @option{-w} (wide) is used. Default: -@smallexample -%D %-.35P %-.38A %22.108t %22.132T\n%/%48|%-.38A %22.108t %22.132T\n -@end smallexample -@end table - -@subsection Standard queries - -If your ledger file uses the standard top-level accounts: Assets, -Liabilities, Income, Expenses, Equity: then the following queries will -enable you to generate some typical accounting reports from your data. - -Your @emph{net worth} can be determined by balancing assets against -liabilities: - -@example -ledger bal ^assets ^liab -@end example - -By removing long-term investment and loan accounts, you can see your -current net liquidity (or liquid net worth): - -@example -ledger bal ^assets ^liab -retirement -brokerage -loan -@end example - -Balancing expenses against income yields your @emph{cash flow}, or net -profit/loss: - -@example -ledger bal ^exp ^inc -@end example - -In this case, if the number is positive it means you spent more than -you earned during the report period. - -@c ---------------------------------------------------------------------- - -The most often used command is the ``balance'' command: - -@example -export LEDGER=/home/johnw/doc/ledger.dat -ledger balance -@end example - -Here I've set my Ledger environment variable to point to where my -ledger file is hiding. Thereafter, I needn't specify it again. - -@subsection Reporting balance totals - -The balance command prints out the summarized balances of all my -top-level accounts, excluding sub-accounts. In order to see the -balances for a specific account, just specify a regular expression -after the balance command: - -@example -ledger balance expenses:food -@end example - -This will show all the money that's been spent on food, since the -beginning of the ledger. For food spending just this month -(September), use: - -@example -ledger -p sep balance expenses:food -@end example - -Or maybe you want to see all of your assets, in which case the -s -(show sub-accounts) option comes in handy: - -@example -ledger -s balance ^assets -@end example - -To exclude a particular account, use a regular expression with a -leading minus sign. The following will show all expenses, but without -food spending: - -@example -ledger balance expenses -food -@end example - -@subsection Reporting percentages - -There is no built-in way to report transaction amounts or account -balances in terms of percentages - -@node Commands, Options, Usage overview, Running Ledger -@section Commands - -@subsection balance - -The @command{balance} command reports the current balance of all -accounts. It accepts a list of optional regexps, which confine the -balance report to the matching accounts. If an account contains -multiple types of commodities, each commodity's total is reported -separately. - -@subsection register - -The @command{register} command displays all the transactions occurring -in a single account, line by line. The account regexp must be -specified as the only argument to this command. If any regexps occur -after the required account name, the register will contain only those -transactions that match. Very useful for hunting down a particular -transaction. - -The output from @command{register} is very close to what a typical -checkbook, or single-account ledger, would look like. It also shows a -running balance. The final running balance of any register should -always be the same as the current balance of that account. - -If you have Gnuplot installed, you may plot the amount or running -total of any register by using the script @file{report}, which is -included in the Ledger distribution. The only requirement is that you -add either @option{-j} or @option{-J} to your register command, in -order to plot either the amount or total column, respectively. - -@subsection print - -The @command{print} command prints out ledger entries in a textual -format that can be parsed by Ledger. They will be properly formatted, -and output in the most economic form possible. The ``print'' command -also takes a list of optional regexps, which will cause only those -transactions which match in some way to be printed. - -The @command{print} command can be a handy way to clean up a ledger -file whose formatting has gotten out of hand. - -@subsection output - -The @command{output} command is very similar to the @command{print} -command, except that it attempts to replicate the specified ledger -file exactly. The format of the command is: - -@example -ledger -f FILENAME output FILENAME -@end example - -Where @file{FILENAME} is the name of the ledger file to output. The -reason for specifying this command is that only entries contained -within that file will be output, and not an included entries (as can -happen with the @command{print} command). - -@subsection xml - -The @command{xml} command outputs results similar to what -@command{print} and @command{register} display, but as an XML form. -This data can then be read in and processed. Use the -@option{--totals} option to include the running total with each -transaction. - -@subsection emacs - -The @command{emacs} command outputs results in a form that can be read -directly by Emacs Lisp. The format of the sexp is: - -@example -((BEG-POS CLEARED DATE CODE PAYEE - (ACCOUNT AMOUNT)...) ; list of transactions - ...) ; list of entries -@end example - -@subsection equity - -The @command{equity} command prints out accounts balances as if they -were entries. This makes it easy to establish the starting balances -for an account, such as when @ref{Archiving previous years}. - -@subsection prices - -The @command{prices} command displays the price history for matching -commodities. The @option{-A} flag is useful with this report, to -display the running average price, or @option{-D} to show each price's -deviation from that average. - -There is also a @command{pricesdb} command which outputs the same -information as @command{prices}, but does in a format that can be -parsed by Ledger. - -@subsection entry - -The @command{entry} commands simplifies the creation of new entries. -It works on the principle that 80% of all transactions are variants of -earlier transactions. Here's how it works: - -Say you currently have this transaction in your ledger file: - -@smallexample -2004/03/15 * Viva Italiano - Expenses:Food $12.45 - Expenses:Tips $2.55 - Liabilities:MasterCard $-15.00 -@end smallexample - -Now it's @samp{2004/4/9}, and you've just eating at @samp{Viva -Italiano} again. The exact amounts are different, but the overall -form is the same. With the @command{entry} command you can type: - -@example -ledger entry 2004/4/9 viva food 11 tips 2.50 -@end example - -This produces the following output: - -@smallexample -2004/04/09 Viva Italiano - Expenses:Food $11.00 - Expenses:Tips $2.50 - Liabilities:MasterCard $-13.50 -@end smallexample - -It works by finding a past transaction matching the regular expression -@samp{viva}, and assuming that any accounts or amounts specified will -be similar to that earlier transaction. If Ledger does not succeed in -generating a new entry, an error is printed and the exit code is set -to @samp{1}. - -There is a shell script in the distribution's @file{scripts} directory -called @file{entry}, which simplifies the task of adding a new entry -to your ledger. It launches @command{vi} to confirm that the entry -looks appropriate. - -Here are a few more examples of the @command{entry} command, assuming -the above journal entry: - -@example -ledger entry 4/9 viva 11.50 -ledger entry 4/9 viva 11.50 checking # (from `checking') -ledger entry 4/9 viva food 11.50 tips 8 -ledger entry 4/9 viva food 11.50 tips 8 cash -ledger entry 4/9 viva food $11.50 tips $8 cash -ledger entry 4/9 viva dining "DM 11.50" -@end example - -@node Options, Format strings, Commands, Running Ledger -@section Options - -With all of the reports, command-line options are useful to modify the -output generated. These command-line options always occur before the -command word. This is done to distinguish options from exclusive -regular expressions, which also begin with a dash. The basic form for -most commands is: - -@example -ledger [OPTIONS] COMMAND [REGEXPS...] [-- [REGEXPS...]] -@end example - -The @var{OPTIONS} and @var{REGEXPS} expressions are both optional. -You could just use @samp{ledger balance}, without any options---which -prints a summary of all accounts. But for more specific reporting, or -to change the appearance of the output, options are needed. - -@menu -* Basic options:: -* Report filtering:: -* Output customization:: -* Commodity reporting:: -* Environment variables:: -@end menu - -@node Basic options, Report filtering, Options, Options -@subsection Basic options - -These are the most basic command options. Most likely, the user will -want to set them using @ref{Environment variables}, instead of using -actual command-line options: - -@option{--help} (@option{-h}) prints a summary of all the options, and -what they are used for. This can be a handy way to remember which -options do what. This help screen is also printed if ledger is run -without a command. - -@option{--version} (@option{-v}) prints the current version of ledger -and exits. This is useful for sending bug reports, to let the author -know which version of ledger you are using. - -@option{--file FILE} (@option{-f FILE}) reads FILE as a ledger file. -This command may be used multiple times. FILE may also be a list of -file names separated by colons. Typically, the environment variable -@env{LEDGER_FILE} is set, rather than using this command-line option. - -@option{--output FILE} (@option{-o FILE}) redirects output from any -command to @var{FILE}. By default, all output goes to standard -output. - -@option{--init-file FILE} (@option{-i FILE}) causes FILE to be read by -ledger before any other ledger file. This file may not contain any -transactions, but it may contain option settings. To specify options -in the init file, use the same syntax as the command-line. Here's an -example init file: - -@smallexample ---price-db ~/finance/.pricedb - -; ~/.ledgerrc ends here -@end smallexample - -Option settings on the command-line or in the environment always take -precedence over settings in the init file. - -@option{--cache FILE} identifies FILE as the default binary cache -file. That is, if the ledger files to be read are specified using the -environment variable @env{LEDGER_FILE}, then whenever a command is -finished a binary copy will be written to the specified cache, to -speed up the loading time of subsequent queries. This filename can -also be given using the environment variable @env{LEDGER_CACHE}, or by -putting the option into your init file. The @option{--no-cache} -option causes Ledger to always ignore the binary cache. - -@option{--account NAME} (@option{-a NAME}) specifies the default -account which QIF file transactions are assumed to relate to. - -@node Report filtering, Output customization, Basic options, Options -@subsection Report filtering - -These options change which transactions affect the outcome of a -report, in ways other than just using regular expressions: - -@option{--current}(@option{-c}) displays only entries occurring on or -before the current date. - -@option{--begin DATE} (@option{-b DATE}) constrains the report to -entries on or after @var{DATE}. Only entries after that date will be -calculated, which means that the running total in the balance report -will always start at zero with the first matching entry. (Note: This -is different from using @option{--display} to constrain what is -displayed). - -@option{--end DATE} (@option{-e DATE}) constrains the report so that -entries on or after @var{DATE} are not considered. The ending date -is inclusive. - -@option{--period STR} (@option{-p STR}) sets the reporting period -to @var{STR}. This will subtotal all matching entries within each -period separately, making it easy to see weekly, monthly, quarterly, -etc., transaction totals. A period string can even specify the -beginning and end of the report range, using simple terms like ``last -june'' or ``next month''. For more using period expressions, see -@ref{Period expressions}. - -@option{--period-sort EXPR} sorts the transactions within each -reporting period using the value expression @var{EXPR}. This is most -often useful when reporting monthly expenses, in order to view the -highest expense categories at the top of each month: - -@example -ledger -M --period-sort -At reg ^Expenses -@end example - -@option{--cleared} (@option{-C}) displays only transactions whose entry -has been marked ``cleared'' (by placing an asterix to the right of the -date). - -@option{--uncleared} (@option{-U}) displays only transactions whose -entry has not been marked ``cleared'' (i.e., if there is no asterix to -the right of the date). - -@option{--real} (@option{-R}) displays only real transactions, not -virtual. (A virtual transaction is indicated by surrounding the -account name with parentheses or brackets; see the section on using -virtual transactions for more information). - -@option{--actual} (@option{-L}) displays only actual transactions, and -not those created due to automated transactions. - -@option{--related} (@option{-r}) displays transactions that are -related to whichever transactions would otherwise have matched the -filtering criteria. In the register report, this shows where money -went to, or the account it came from. In the balance report, it shows -all the accounts affected by entries having a related transaction. -For example, if a file had this entry: - -@smallexample -2004/03/20 Safeway - Expenses:Food $65.00 - Expenses:Cash $20.00 - Assets:Checking $-85.00 -@end smallexample - -And the register command was: - -@example -ledger -r register food -@end example - -The following would be output, showing the transactions related to the -transaction that matched: - -@smallexample -2004/03/20 Safeway Expenses:Cash $-20.00 $-20.00 - Assets:Checking $85.00 $65.00 -@end smallexample - -@option{--budget} is useful for displaying how close your transactions -meet your budget. @option{--add-budget} also shows unbudgeted -transactions, while @option{--unbudgeted} shows only those. -@option{--forecast} is a related option that projects your budget into -the future, showing how it will affect future balances. -@xref{Budgeting and forecasting}. - -@option{--limit EXPR} (@option{-l EXPR}) limits which transactions -take part in the calculations of a report. - -@option{--amount EXPR} (@option{-t EXPR}) changes the value expression -used to calculate the ``value'' column in the @command{register} -report, the amount used to calculate account totals in the -@command{balance} report, and the values printed in the -@command{equity} report. @xref{Value expressions}. - -@option{--total EXPR} (@option{-T EXPR}) sets the value expression -used for the ``totals'' column in the @command{register} and -@command{balance} reports. - -@node Output customization, Commodity reporting, Report filtering, Options -@subsection Output customization - -These options affect only the output, but not which transactions are -used to create it: - -@option{--collapse} (@option{-n}) causes entries in a -@command{register} report with multiple transactions to be collapsed -into a single, subtotaled entry. - -@option{--subtotal} (@option{-s}) causes all entries in a -@command{register} report to be collapsed into a single, subtotaled -entry. - -@option{--by-payee} (@option{-P}) reports subtotals by payee. - -@option{--comm-as-payee} (@option{-x}) changes the payee of every -transaction to be the commodity used in that transaction. This can be -useful when combined with other options, such as @option{-s}. - -@option{--empty} (@option{-E}) includes even empty accounts in the -@command{balance} report. - -@option{--weekly} (@option{-W}) reports transaction totals by the -week. The week begins on whichever day of the week begins the month -containing that transaction. To set a specific begin date, use a -period string, such as @samp{weekly from DATE}. @option{--monthly} -(@option{-M}) reports transaction totals by month; @option{--yearly} -(@option{-Y}) reports transaction totals by year. For more complex -period, using the @option{--period} option described above. - -@option{--dow} reports transactions totals for each day of the week. -This is an easy way to see if weekend spending is more than on -weekdays. - -@option{--sort EXPR} (@option{-S EXPR}) sorts a report by comparing -the values determined using the value expression @var{EXPR}. For -example, using @option{-S -UT} in the balance report will sort account -balances from greatest to least, using the absolute value of the -total. For more on how to use value expressions, see @ref{Value -expressions}. - -@option{--wide} (@option{-w}) causes the default @command{register} -report to assume 132 columns instead of 80. - -@option{--head} causes only the first N entries to be printed. This -is different from using the command-line utility @command{head}, which -would limit to the first N transactions. @option{--tail} outputs only -the last N entries. Both options may be used simultaneously. If a -negative amount is given, it will invert the meaning of the flag -(instead of the first five entries being printed, for example, it -would print all but the first five). - -@option{--pager} tells Ledger to pass its output to the given pager -program---very useful when the output is especially long. This -behavior can be made the default by setting the @env{LEDGER_PAGER} -environment variable. - -@option{--average} (@option{-A}) reports the average transaction -value. - -@option{--deviation} (@option{-D}) reports each transaction's -deviation from the average. It is only meaningful in the -@command{register} and @command{prices} reports. - -@option{--percentage} (@option{-%}) shows account subtotals in the -@command{balance} report as percentages of the parent account. - -@option{--totals} include running total information in the -@command{xml} report. - -@option{--amount-data} (@option{-j}) changes the @command{register} -report so that it output nothing but the date and the value column, -and the latter without commodities. This is only meaningful if the -report uses a single commodity. This data can then be fed to other -programs, which could plot the date, analyze it, etc. - -@option{--total-data} (@option{-J}) changes the @command{register} -report so that it output nothing but the date and totals column, -without commodities. - -@option{--display EXPR} (@option{-d EXPR}) limits which transactions -or accounts or actually displayed in a report. They might still be -calculated, and be part of the running total of a register report, for -example, but they will not be displayed. This is useful for seeing -last month's checking transactions, against a running balance which -includes all transaction values: - -@example -ledger -d "d>=[last month]" reg checking -@end example - -The output from this command is very different from the following, -whose running total includes only transactions from the last month -onward: - -@example -ledger -p "last month" reg checking -@end example - -Which is more useful depends on what you're looking to know: the total -amount for the reporting range (@option{-p}), or simply a display -restricted to the reporting range (using @option{-d}). - -@option{--date-format STR} (@option{-y STR}) changes the basic date -format used by reports. The default uses a date like 2004/08/01, -which represents the default date format of @samp{%Y/%m/%d}. To -change the way dates are printed in general, the easiest way is to put -@option{--date-format FORMAT} in the Ledger initialization file -@file{~/.ledgerrc} (or the file referred to by @env{LEDGER_INIT}). - -@option{--format STR} (@option{-F STR}) sets the reporting format for -whatever report ledger is about to make. @xref{Format strings}. -There are also specific format commands for each report type: - -@itemize -@item @option{--balance-format STR} -@item @option{--register-format STR} -@item @option{--print-format STR} -@item @option{--plot-amount-format STR} (-j @command{register}) -@item @option{--plot-total-format STR} (-J @command{register}) -@item @option{--equity-format STR} -@item @option{--prices-format STR} -@item @option{--wide-register-format STR} (-w @command{register}) -@end itemize - -@node Commodity reporting, Environment variables, Output customization, Options -@subsection Commodity reporting - -These options affect how commodity values are displayed: - -@option{--price-db FILE} sets the file that is used for recording -downloaded commodity prices. It is always read on startup, to -determine historical prices. Other settings can be placed in this -file manually, to prevent downloading quotes for a specific, for -example. This is done by adding a line like the following: - -@example -; Don't download quotes for the dollar, or timelog values -N $ -N h -@end example - -@option{--price-exp MINS} (@option{-L MINS}) sets the expected -freshness of price quotes, in minutes. That is, if the last known -quote for any commodity is older than this value---and if -@option{--download} is being used---then the Internet will be -consulted again for a newer price. Otherwise, the old price is still -considered to be fresh enough. - -@option{--download} (@option{-Q}) causes quotes to be automagically -downloaded, as needed, by running a script named @command{getquote} -and expecting that script to return a value understood by ledger. A -sample implementation of a @command{getquote} script, implemented in -Perl, is provided in the distribution. Downloaded quote price are -then appended to the price database, usually specified using the -environment variable @env{LEDGER_PRICE_DB}. - -There are several different ways that ledger can report the totals it -displays. The most flexible way to adjust them is by using value -expressions, and the @option{-t} and @option{-T} options. However, -there are also several ``default'' reports, which will satisfy most -users basic reporting needs: - -@table @code -@item -O, --quantity -Reports commodity totals (this is the default) - -@item -B, --basis -Reports the cost basis for all transactions. - -@item -V, --market -Reports the last known market value for all commodities. - -@item -g, --performance -Reports the net gain/loss for each transaction in a @command{register} -report. - -@item -G --gain -Reports the net gain/loss for all commodities in the report that have -a price history. -@end table - -@node Environment variables, , Commodity reporting, Options -@subsection Environment variables - -Every option to ledger may be set using an environment variable. If -an option has a long name such @option{--this-option}, setting the -environment variable @env{LEDGER_THIS_OPTION} will have the same -affect as specifying that option on the command-line. Options on the -command-line always take precedence over environment variable -settings, however. - -Note that you may also permanently specify option values by placing -option settings in the file @file{~/.ledgerrc}, for example: - -@example ---cache /tmp/.mycache -@end example - -@node Format strings, Value expressions, Options, Running Ledger -@section Format strings - -Format strings may be used to change the output format of reports. -They are specified by passing a formatting string to the -@option{--format} (@option{-F}) option. Within that string, -constructs are allowed which make it possible to display the various -parts of an account or transaction in custom ways. - -Within a format strings, a substitution is specified using a percent -character (@samp{%}). The basic format of all substitutions is: - -@example -%[-][MIN WIDTH][.MAX WIDTH]EXPR -@end example - -If the optional minus sign (@samp{-}) follows the percent character, -whatever is substituted will be left justified. The default is right -justified. If a minimum width is given next, the substituted text -will be at least that wide, perhaps wider. If a period and a maximum -width is given, the substituted text will never be wider than this, -and will be truncated to fit. Here are some examples: - -@example -%-P An entry's payee, left justified -%20P The same, right justified, at least 20 chars wide -%.20P The same, no more than 20 chars wide -%-.20P Left justified, maximum twenty chars wide -@end example - -The expression following the format constraints can be a single -letter, or an expression enclosed in parentheses or brackets. The -allowable expressions are: - -@table @code -@item % -Inserts a percent sign. - -@item t -Inserts the results of the value expression specified by @option{-t}. -If @option{-t} was not specified, the current report style's value -expression is used. - -@item T -Inserts the results of the value expression specified by @option{-T}. -If @option{-T} was not specified, the current report style's value -expression is used. - -@item | -Inserts a single space. This is useful if a width is specified, for -inserting a certain number of spaces. - -@item _ -Inserts a space for each level of an account's depth. That is, if an -account has two parents, this construct will insert two spaces. If a -minimum width is specified, that much space is inserted for each level -of depth. Thus @samp{%5_}, for an account with four parents, will -insert twenty spaces. - -@item (EXPR) -Inserts the amount resulting from the value expression given in -parentheses. To insert five times the total value of an account, for -example, one could say @samp{%12(5*O)}. Note: It's important to put -the five first in that expression, so that the commodity doesn't get -stripped from the total. - -@item [DATEFMT] -Inserts the result of formatting a transaction's date with a date -format string, exactly like those supported by @code{strftime}. For -example: @samp{%[%Y/%m/%d %H:%M:%S]}. - -@item S -Insert the pathname of the file from which the entry's data was read. - -@item B -Inserts the beginning character position of that entry within the file. - -@item b -Inserts the beginning line of that entry within the file. - -@item E -Inserts the ending character position of that entry within the file. - -@item e -Inserts the ending line of that entry within the file. - -@item D -By default, this is the same as @samp{%[%Y/%m%/d]}. The date format -used can be changed at any time with the @option{-y} flag, however. -Using @samp{%D} gives the user more control over the way dates are -output. - -@item d -This is the same as the @samp{%D} option, unless the entry has an -effective date, in which case it prints -@samp{[ACTUAL_DATE=EFFECtIVE_DATE]}. - -@item X -If a transaction has been cleared, this inserts @samp{*} followed by a -space; otherwise nothing is inserted. - -@item Y -This is the same as @samp{%X}, except that it only displays a state -character if all of the member transactions have the same state. - -@item C -Inserts the checking number for an entry, in parentheses, followed by -a space; if none was specified, nothing is inserted. - -@item P -Inserts the payee related to a transaction. - -@item a -Inserts the optimal short name for an account. This is normally used -in balance reports. It prints a parent account's name if that name -has not been printed yet, otherwise it just prints the account's name. - -@item A -Inserts the full name of an account. - -@item W -This is the same as @samp{%A}, except that it first displays the -transaction's state @emph{if the entry's transaction states are not -all the same}, followed by the full account name. This is offered as -a printing optimization, so that combined with @samp{%Y}, only the -minimum amount of state detail is printed. - -@item o -Inserts the ``optimized'' form of a transaction's amount. This is -used by the print report. In some cases, this inserts nothing; in -others, it inserts the transaction amount and its cost. It's use is -not recommend unless you are modifying the print report. - -@item n -Inserts the note associated with a transaction, preceded by two spaces -and a semi-colon, if it exists. Thus, no none becomes an empty -string, while the note @samp{foo} is substituted as @samp{ ; foo}. - -@item N -Inserts the note associated with a transaction, if one exists. - -@item / -The @samp{%/} construct is special. It separates a format string -between what is printed for the first transaction of an entry, and -what is printed for all subsequent transactions. If not used, the -same format string is used for all transactions. -@end table - -@node Value expressions, Period expressions, Format strings, Running Ledger -@section Value expressions - -Value expressions are an expression language used by Ledger to -calculate values used by the program for many different purposes: - -@enumerate -@item -The values displayed in reports -@item -For predicates (where truth is anything non-zero), to determine which -transactions are calculated (@option{-l}) or displayed (@option{-d}). -@item -For sorting criteria, to yield the sort key. -@item -In the matching criteria used by automated transactions. -@end enumerate - -Value expressions support most simple math and logic operators, in -addition to a set of one letter functions and variables. A function's -argument is whatever follows it. The following is a display predicate -that I use with the @command{balance} command: - -@example -ledger -d /^Liabilities/?T<0:UT>100 balance -@end example - -The effect is that account totals are displayed only if: 1) A -Liabilities account has a total less than zero; or 2) the absolute -value of the account's total exceeds 100 units of whatever commodity -contains. If it contains multiple commodities, only one of them must -exceed 100 units. - -Display predicates are also very handy with register reports, to -constrain which entries are printed. For example, the following -command shows only entries from the beginning of the current month, -while still calculating the running balance based on all entries: - -@example -ledger -d "d>[this month]" register checking -@end example - -This advantage to this command's complexity is that it prints the -running total in terms of all entries in the register. The following, -simpler command is similar, but totals only the displayed -transactions: - -@example -ledger -b "this month" register checking -@end example - -@subsection Variables - -Below are the one letter variables available in any value expression. -For the register and print commands, these variables relate to -individual transactions, and sometimes the account affected by a -transaction. For the balance command, these variables relate to -accounts---often with a subtle difference in meaning. The use of each -variable for both is specified. - -@table @code -@item t -This maps to whatever the user specified with @option{-t}. In a -register report, @option{-t} changes the value column; in a balance -report, it has no meaning by default. If @option{-t} was not -specified, the current report style's value expression is used. - -@item T -This maps to whatever the user specified with @option{-T}. In a -register report, @option{-T} changes the totals column; in a balance -report, this is the value given for each account. If @option{-T} was -not specified, the current report style's value expression is used. - -@item m -This is always the present moment/date. -@end table - -@subsubsection Transaction/account details - -@table @code -@item d -A transaction's date, as the number of seconds past the epoch. This -is always ``today'' for an account. - -@item a -The transaction's amount; the balance of an account, without -considering children. - -@item b -The cost of a transaction; the cost of an account, without its -children. - -@item v -The market value of a transaction, or an account without its children. - -@item g -The net gain (market value minus cost basis), for a transaction or an -account without its children. It is the same as @samp{v-b}. - -@item l -The depth (``level'') of an account. If an account has one parent, -it's depth is one. - -@item n -The index of a transaction, or the count of transactions affecting an -account. - -@item X -1 if a transaction's entry has been cleared, 0 otherwise. - -@item R -1 if a transaction is not virtual, 0 otherwise. - -@item Z -1 if a transaction is not automated, 0 otherwise. -@end table - -@subsubsection Calculated totals - -@table @code -@item O -The total of all transactions seen so far, or the total of an account -and all its children. - -@item N -The total count of transactions affecting an account and all its -children. - -@item B -The total cost of all transactions seen so far; the total cost of an -account and all its children. - -@item V -The market value of all transactions seen so far, or of an account and -all its children. - -@item G -The total net gain (market value minus cost basis), for a series of -transactions, or an account and its children. It is the same as -@samp{V-B}. -@end table - -@subsection Functions - -The available one letter functions are: - -@table @code -@item - -Negates the argument. - -@item U -The absolute (unsigned) value of the argument. - -@item S -Strips the commodity from the argument. - -@item A -The arithmetic mean of the argument; @samp{Ax} is the same as -@samp{x/n}. - -@item P -The present market value of the argument. The syntax @samp{P(x,d)} is -supported, which yields the market value at time @samp{d}. If no date -is given, then the current moment is used. -@end table - -@subsection Operators - -The binary and ternary operators, in order of precedence, are: - -@enumerate -@item @samp{* /} -@item @samp{+ -} -@item @samp{! < > =} -@item @samp{& | ?:} -@end enumerate - -@subsection Complex expressions - -More complicated expressions are possible using: - -@table @code -@item NUM -A plain integer represents a commodity-less amount. - -@item @{AMOUNT@} -An amount in braces can be any kind of amount supported by ledger, -with or without a commodity. Use this for decimal values. - -@item /REGEXP/ -@item W/REGEXP/ -A regular expression that matches against an account's full name. If -a transaction, this will match against the account affected by the -transaction. - -@item //REGEXP/ -@item p/REGEXP/ -A regular expression that matches against an entry's payee name. - -@item ///REGEXP/ -@item w/REGEXP/ -A regular expression that matches against an account's base name. If -a transaction, this will match against the account affected by the -transaction. - -@item c/REGEXP/ -A regular expression that matches against the entry code (the text -that occurs between parentheses before the payee name). - -@item e/REGEXP/ -A regular expression that matches against a transaction's note, or -comment field. - -@item (EXPR) -A sub-expression is nested in parenthesis. This can be useful passing -more complicated arguments to functions, or for overriding the natural -precedence order of operators. - -@item [DATE] -Useful specifying a date in plain terms. For example, you could say -@samp{[2004/06/01]}. -@end table - -@node Period expressions, File format, Value expressions, Running Ledger -@section Period expressions - -A period expression indicates a span of time, or a reporting interval, -or both. The full syntax is: - -@example -[INTERVAL] [BEGIN] [END] -@end example - -The optional @var{INTERVAL} part may be any one of: - -@example -every day -every week -every monthly -every quarter -every year -every N days # N is any integer -every N weeks -every N months -every N quarters -every N years -daily -weekly -biweekly -monthly -bimonthly -quarterly -yearly -@end example - -After the interval, a begin time, end time, both or neither may be -specified. As for the begin time, it can be either of: - -@example -from -since -@end example - -The end time can be either of: - -@example -to -until -@end example - -Where @var{SPEC} can be any of: - -@example -2004 -2004/10 -2004/10/1 -10/1 -october -oct -this week # or day, month, quarter, year -next week -last week -@end example - -The beginning and ending can be given at the same time, if it spans a -single period. In that case, just use @var{SPEC} by itself. In that -case, the period @samp{oct}, for example, will cover all the days in -october. The possible forms are: - -@example - -in -@end example - -Here are a few examples of period expressions: - -@example -monthly -monthly in 2004 -weekly from oct -weekly from last month -from sep to oct -from 10/1 to 10/5 -monthly until 2005 -from apr -until nov -last oct -weekly last august -@end example - -@node File format, Some typical queries, Period expressions, Running Ledger -@section File format - -The ledger file format is quite simple, but also very flexible. It -supports many options, though typically the user can ignore most of -them. They are summarized below. - -The initial character of each line determines what the line means, and -how it should be interpreted. Allowable initial characters are: - -@table @code -@item NUMBER -A line beginning with a number denotes an entry. It may be followed -by any number of lines, each beginning with whitespace, to denote the -entry's account transactions. The format of the first line is: - -@example -DATE[=EDATE] [*|!] [(CODE)] DESC -@end example - -If @samp{*} appears after the date (with optional effective date), it -indicates the entry is ``cleared'', which can mean whatever the user -wants it t omean. If @samp{!} appears after the date, it indicates d -the entry is ``pending''; i.e., tentatively cleared from the user's -point of view, but not yet actually cleared. If a @samp{CODE} appears -in parentheses, it may be used to indicate a check number, or the type -of the transaction. Following these is the payee, or a description of -the transaction. - -The format of each following transaction is: - -@example - ACCOUNT AMOUNT [; NOTE] -@end example - -The @samp{ACCOUNT} may be surrounded by parentheses if it is a virtual -transactions, or square brackets if it is a virtual transactions that -must balance. The @samp{AMOUNT} can be followed by a per-unit -transaction cost, by specifying @samp{@ AMOUNT}, or a complete -transaction cost with @samp{@@ AMOUNT}. Lastly, the @samp{NOTE} may -specify an actual and/or effective date for the transaction by using -the syntax @samp{[ACTUAL_DATE]} or @samp{[=EFFECTIVE_DATE]} or -@samp{[ACTUAL_DATE=EFFECtIVE_DATE]}. - -@item = -An automated entry. A value expression must appear after the equal -sign. - -After this initial line there should be a set of one or more -transactions, just as if it were normal entry. If the amounts of the -transactions have no commodity, they will be applied as modifiers to -whichever real transaction is matched by the value expression. - -@item ~ -A period entry. A period expression must appear after the tilde. - -After this initial line there should be a set of one or more -transactions, just as if it were normal entry. - -@item ! -A line beginning with an exclamation mark denotes a command directive. -It must be immediately followed by the command word. The supported -commands are: - -@table @samp -@item !include -Include the stated ledger file. - -@item !account -The account name is given is taken to be the parent of all -transactions that follow, until @samp{!end} is seen. - -@item !end -Ends an account block. -@end table - -@item ; -A line beginning with a colon indicates a comment, and is ignored. - -@item Y -If a line begins with a capital Y, it denotes the year used for all -subsequent entries that give a date without a year. The year should -appear immediately after the Y, for example: @samp{Y2004}. This is -useful at the beginning of a file, to specify the year for that file. -If all entries specify a year, however, this command has no effect. - -@item P -Specifies a historical price for a commodity. These are usually found -in a pricing history file (see the @option{-Q} option). The syntax -is: -@example -P DATE SYMBOL PRICE -@end example - -@item N SYMBOL -Indicates that pricing information is to be ignored for a given -symbol, nor will quotes ever be downloaded for that symbol. Useful -with a home currency, such as the dollar ($). It is recommended that -these pricing options be set in the price database file, which -defaults to @file{~/.pricedb}. The syntax for this command is: -@example -N SYMBOL -@end example - -@item D AMOUNT -Specifies the default commodity to use, by specifying an amount in the -expected format. The @command{entry} command will use this commodity -as the default when none other can be determined. This command may be -used multiple times, to set the default flags for different -commodities; whichever is seen last is used as the default commodity. -For example, to set US dollars as the default commodity, while also -setting the thousands flag and decimal flag for that commodity, use: -@example -D $1,000.00 -@end example - -@item C AMOUNT1 = AMOUNT2 -Specifies a commodity conversion, where the first amount is given to -be equivalent to the second amount. The first amount should use the -decimal precision desired during reporting: -@example -C 1.00 Kb = 1024 bytes -@end example - -@item i, o, b, h -These four relate to timeclock support, which permits ledger to read -timelog files. See the timeclock's documentation for more info on the -syntax of its timelog files. -@end table - -@node Some typical queries, Budgeting and forecasting, File format, Running Ledger -@section Some typical queries - -A query such as the following shows all expenses since last -October, sorted by total: - -@example -ledger -b "last oct" -s -S T bal ^expenses -@end example - -From left to right the options mean: Show entries since October, 2003; -show all sub-accounts; sort by the absolute value of the total; and -report the balance for all expenses. - -@subsection Reporting monthly expenses - -The following query makes it easy to see monthly expenses, with each -month's expenses sorted by the amount: - -@example -ledger -M --period-sort t reg ^expenses -@end example - -Now, you might wonder where the money came from to pay for these -things. To see that report, add @option{-r}, which shows the -``related account'' transactions: - -@example -ledger -M --period-sort t -r reg ^expenses -@end example - -But maybe this prints too much information. You might just want to -see how much you're spending with your MasterCard. That kind of query -requires the use of a display predicate, since the transactions -calculated must match @samp{^expenses}, while the transactions -displayed must match @samp{mastercard}. The command would be: - -@example -ledger -M -r -d /mastercard/ reg ^expenses -@end example - -This query says: Report monthly subtotals; report the ``related -account'' transactions; display only related transactions whose -account matches @samp{mastercard}, and base the calculation on -transactions matching @samp{^expenses}. - -This works just as well for report the overall total, too: - -@example -ledger -s -r -d /mastercard/ reg ^expenses -@end example - -The @option{-s} option subtotals all transactions, just as @option{-M} -subtotaled by the month. The running total in both cases is off, -however, since a display expression is being used. - -@subsection Visualizing with Gnuplot - -If you have @command{Gnuplot} installed, you can graph any of the -above register reports. The script to do this is included in the -ledger distribution, and is named @file{scripts/report}. Install -@file{report} anywhere along your @env{PATH}, and then use -@command{report} instead of @command{ledger} when doing a register -report. The only thing to keep in mind is that you must specify -@option{-j} or @option{-J} to indicate whether Gnuplot should plot the -amount, or the running total. For example, this command plots total -monthly expenses made on your MasterCard. - -@example -report -j -M -r -d /mastercard/ reg ^expenses -@end example - -The @command{report} script is a very simple Bourne shell script, that -passes a set of scripted commands to Gnuplot. Feel free to modify the -script to your liking, since you may prefer histograms to line plots, -for example. - -@subsubsection Typical plots - -Here are some useful plots: - -@smallexample -report -j -M reg ^expenses # monthly expenses -report -J reg checking # checking account balance -report -J reg ^income ^expenses # cash flow report - -# net worth report, ignoring non-$ transactions - -report -J -l "Ua>=@{\$0.01@}" reg ^assets ^liab - -# net worth report starting last February. the use of a display -# predicate (-d) is needed, otherwise the balance will start at -# zero, and thus the y-axis will not reflect the true balance - -report -J -l "Ua>=@{\$0.01@}" -d "d>=[last feb]" reg ^assets ^liab -@end smallexample - -The last report uses both a calculation predicate (@option{-l}) and a -display predicate (@option{-d}). The calculation predicates limits -the report to transactions whose amount is greater than $1 (which can -only happen if the transaction amount is in dollars). The display -predicate limits the entries @emph{displayed} to just those since last -February, even those entries from before then will be computed as part -of the balance. - -@node Budgeting and forecasting, , Some typical queries, Running Ledger -@section Budgeting and forecasting - -@subsection Budgeting - -Keeping a budget allows you to pay closer attention to your income and -expenses, by reporting how far your actual financial activity is from -your expectations. - -To start keeping a budget, put some period entries at the top of your -ledger file. A period entry is almost identical to a regular entry, -except that it begins with a tilde and has a period expression in -place of a payee. For example: - -@smallexample -~ Monthly - Expenses:Rent $500.00 - Expenses:Food $450.00 - Expenses:Auto:Gas $120.00 - Expenses:Insurance $150.00 - Expenses:Phone $125.00 - Expenses:Utilities $100.00 - Expenses:Movies $50.00 - Expenses $200.00 ; all other expenses - Assets - -~ Yearly - Expenses:Auto:Repair $500.00 - Assets -@end smallexample - -These two period entries give the usual monthly expenses, as well as -one typical yearly expense. For help on finding out what your average -monthly expense is for any category, use a command like: - -@example -ledger -p "this year" -MAs bal ^expenses -@end example - -The reported totals are the current year's average for each account. - -Once these period entries are defined, creating a budget report is as -easy as adding @option{--budget} to the command-line. For example, a -typical monthly expense report would be: - -@example -ledger -M reg ^exp -@end example - -To see the same report balanced against your budget, use: - -@example -ledger --budget -M reg ^exp -@end example - -A budget report includes only those accounts that appear in the -budget. To see all expenses balanced against the budget, use -@option{--add-budget}. You can even see only the unbudgeted expenses -using @option{--unbudgeted}: - -@example -ledger --unbudgeted -M reg ^exp -@end example - -You can also use these flags with the @command{balance} command. - -@subsection Forecasting - -Sometimes it's useful to know what your finances will look like in the -future, such as determining when an account will reach zero. Ledger -makes this easy to do, using the same period entries as are used for -budgeting. An example forecast report can be generated with: - -@example -ledger --forecast "T>@{\$-500.00@}" register ^assets ^liabilities -@end example - -This report continues outputting transactions until the running total -is greater than $-500.00. A final transaction is always output, to -show you what the total afterwards would be. - -Forecasting can also be used with the balance report, but by date -only, and not against the running total: - -@example -ledger --forecast "d<[2010]" bal ^assets ^liabilities -@end example - -@node Keeping a ledger, Using XML, Running Ledger, Top -@chapter Keeping a ledger - -The most important part of accounting is keeping a good ledger. If -you have a good ledger, tools can be written to work whatever -mathematically tricks you need to better understand your spending -patterns. Without a good ledger, no tool, however smart, can help -you. - -The Ledger program aims at making ledger entry as simple as possible. -Since it is a command-line tool, it does not provide a user interface -for keeping a ledger. If you like, you may use GnuCash to maintain -your ledger, in which case the Ledger program will read GnuCash's data -files directly. In that case, read the GnuCash manual now, and skip -to the next chapter. - -If you are not using GnuCash, but a text editor to maintain your -ledger, read on. Ledger has been designed to make data entry as -simple as possible, by keeping the ledger format easy, and also by -automagically determining as much information as possible based on the -nature of your entries. - -For example, you do not need to tell Ledger about the accounts you -use. Any time Ledger sees a transaction involving an account it knows -nothing about, it will create it. If you use a commodity that is new -to Ledger, it will create that commodity, and determine its display -characteristics (placement of the symbol before or after the amount, -display precision, etc) based on how you used the commodity in the -transaction. - -Here is the Pacific Bell example from above, given as a Ledger -transaction: - -@smallexample -9/29 (100) Pacific Bell - Expenses:Utilities:Phone $23.00 - Assets:Checking $-23.00 -@end smallexample - -As you can see, it is very similar to what would be written on paper, -minus the computed balance totals, and adding in account names that -work better with Ledger's scheme of things. In fact, since Ledger is -smart about many things, you don't need to specify the balanced -amount, if it is the same as the first line: - -@smallexample -9/29 (100) Pacific Bell - Expenses:Utilities:Phone $23.00 - Assets:Checking -@end smallexample - -For this entry, Ledger will figure out that $-23.00 must come from -@samp{Assets:Checking} in order to balance the entry. - -@menu -* Stating where money goes:: -* Assets and Liabilities:: -* Commodities and Currencies:: -* Accounts and Inventories:: -* Understanding Equity:: -* Dealing with Petty Cash:: -* Working with multiple funds and accounts:: -* Archiving previous years:: -* Virtual transactions:: -* Automated transactions:: -* Using Emacs to Keep Your Ledger:: -* Using GnuCash to Keep Your Ledger:: -* Using timeclock to record billable time:: -@end menu - -@node Stating where money goes, Assets and Liabilities, Keeping a ledger, Keeping a ledger -@section Stating where money goes - -Accountants will talk of ``credits'' and ``debits'', but the meaning -is often different from the layman's understanding. To avoid -confusion, Ledger uses only subtractions and additions, although the -underlying intent is the same as standard accounting principles. - -Recall that every transaction will involve two or more accounts. -Money is transferred from one or more accounts to one or more other -accounts. To record the transaction, an amount is @emph{subtracted} -from the source accounts, and @emph{added} to the target accounts. - -In order to write a Ledger entry correctly, you must determine where -the money comes from and where it goes to. For example, when you are -paid a salary, you must add money to your bank account and also -subtract it from an income account: - -@smallexample -9/29 My Employer - Assets:Checking $500.00 - Income:Salary $-500.00 -@end smallexample - -Why is the Income a negative figure? When you look at the balance -totals for your ledger, you may be surprised to see that Expenses are -a positive figure, and Income is a negative figure. It may take some -getting used to, but to properly use a general ledger you must think -in terms of how money moves. Rather than Ledger ``fixing'' the minus -signs, let's understand why they are there. - -When you earn money, the money has to come from somewhere. Let's call -that somewhere ``society''. In order for society to give you an -income, you must take money away (withdraw) from society in order to -put it into (make a payment to) your bank. When you then spend that -money, it leaves your bank account (a withdrawal) and goes back to -society (a payment). This is why Income will appear negative---it -reflects the money you have drawn from society---and why Expenses will -be positive---it is the amount you've given back. These additions and -subtractions will always cancel each other out in the end, because you -don't have the ability to create new money: it must always come from -somewhere, and in the end must always leave. This is the beginning of -economy, after which the explanation gets terribly difficult. - -Based on that explanation, here's another way to look at your balance -report: every negative figure means that that account or person or -place has less money now than when you started your ledger; and every -positive figure means that that account or person or place has more -money now that when you started your ledger. Make sense? - -@node Assets and Liabilities, Commodities and Currencies, Stating where money goes, Keeping a ledger -@section Assets and Liabilities - -Assets are money that you have, and Liabilities are money that you -owe. ``Liabilities'' is just a more inclusive name for Debts. - -An Asset is typically increased by transferring money from an Income -account, such as when you get paid. Here is a typical entry: - -@smallexample -2004/09/29 My Employer - Assets:Checking $500.00 - Income:Salary -@end smallexample - -Money, here, comes from an Income account belonging to ``My -Employer'', and is transferred to your checking account. The money is -now yours, which makes it an Asset. - -Liabilities track money owed to others. This can happen when you -borrow money to buy something, or if you owe someone money. Here is -an example of increasing a MasterCard liability by spending money with -it: - -@smallexample -2004/09/30 Restaurant - Expenses:Dining $25.00 - Liabilities:MasterCard -@end smallexample - -The Dining account balance now shows $25 spent on Dining, and a -corresponding $25 owed on the MasterCard---and therefore shown as -$-25.00. The MasterCard liability shows up as negative because it -offsets the value of your assets. - -The combined total of your Assets and Liabilities is your net worth. -So to see your current net worth, use this command: - -@example -ledger balance ^assets ^liabilities -@end example - -Relatedly, your Income accounts show up negative, because they -transfer money @emph{from} an account in order to increase your -assets. Your Expenses show up positive because that is where the -money went to. The combined total of Income and Expenses is your cash -flow. A positive cash flow means you are spending more than you make, -since income is always a negative figure. To see your current cash -flow, use this command: - -@example -ledger balance ^income ^expenses -@end example - -Another common question to ask of your expenses is: How much do I -spend each month on X? Ledger provides a simple way of displaying -monthly totals for any account. Here is an example that summarizes -your monthly automobile expenses: - -@example -ledger -M register expenses:auto -@end example - -This assumes, of course, that you use account names like -@samp{Expenses:Auto:Gas} and @samp{Expenses:Auto:Repair}. - -@subsection Tracking reimbursable expenses - -Sometimes you will want to spend money on behalf of someone else, -which will eventually get repaid. Since the money is still ``yours'', -it is really an asset. And since the expenditure was for someone -else, you don't want it contaminating your Expenses reports. You will -need to keep an account for tracking reimbursements. - -This is fairly easy to do in ledger. When spending the money, spend -it @emph{to} your Assets:Reimbursements, using a different account for -each person or business that you spend money for. For example: - -@smallexample -2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard -@end smallexample - -This shows $100.00 spent on a MasterCard at Circuit City, with the -expense was made on behalf of Company XYZ. Later, when Company XYZ -pays the amount back, the money will transfer from that reimbursement -account back to a regular asset account: - -@smallexample -2004/09/29 Company XYZ - Assets:Checking $100.00 - Assets:Reimbursements:Company XYZ -@end smallexample - -This deposits the money owed from Company XYZ into a checking account, -presumably because they paid the amount back with a check. - -But what to do if you run your own business, and you want to keep -track of expenses made on your own behalf, while still tracking -everything in a single ledger file? This is more complex, because you -need to track two separate things: 1) The fact that the money should -be reimbursed to you, and 2) What the expense account was, so that you -can later determine where your company is spending its money. - -This kind of transaction is best handled with mirrored transactions in -two different files, one for your personal accounts, and one for your -company accounts. But keeping them in one file involves the same -kinds of transactions, so those are what is shown here. First, the -personal entry, which shows the need for reimbursement: - -@smallexample -2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard -@end smallexample - -This is the same as above, except that you own Company XYZ, and are -keeping track of its expenses in the same ledger file. This entry -should be immediately followed by an equivalent entry, which shows the -kind of expense, and also notes the fact that $100.00 is now payable -to you: - -@smallexample -2004/09/29 Circuit City - Company XYZ:Expenses:Computer:Software $100.00 - Company XYZ:Accounts Payable:Your Name -@end smallexample - -This second entry shows that Company XYZ has just spent $100.00 on -software, and that this $100.00 came from Your Name, which must be -paid back. - -These two entries can also be merged, to make things a little clearer. -Note that all amounts must be specified now: - -@smallexample -2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard $-100.00 - Company XYZ:Expenses:Computer:Software $100.00 - Company XYZ:Accounts Payable:Your Name $-100.00 -@end smallexample - -To ``pay back'' the reimbursement, just reverse the order of -everything, except this time drawing the money from a company asset, -paying it to accounts payable, and then drawing it again from the -reimbursement account, and paying it to your personal asset account. -It's easier shown than said: - -@smallexample -2004/10/15 Company XYZ - Assets:Checking $100.00 - Assets:Reimbursements:Company XYZ $-100.00 - Company XYZ:Accounts Payable:Your Name $100.00 - Company XYZ:Assets:Checking $-100.00 -@end smallexample - -And now the reimbursements account is paid off, accounts payable is -paid off, and $100.00 has been effectively transferred from the -company's checking account to your personal checking account. The -money simply ``waited''---in both @samp{Assets:Reimbursements:Company -XYZ}, and @samp{Company XYZ:Accounts Payable:Your Name}---until such -time as it could be paid off. - -The value of tracking expenses from both sides like that is that you -do not contaminate your personal expense report with expenses made on -behalf of others, while at the same time making it possible to -generate accurate reports of your company's expenditures. It is more -verbose than just paying for things with your personal assets, but it -gives you a very accurate information trail. - -The advantage to keep these doubled entries together is that they -always stay in sync. The advantage to keeping them apart is that it -clarifies the transfer's point of view. To keep the transactions in -separate files, just separate the two entries that were joined above. -For example, for both the expense and the pay-back shown above, the -following four entries would be created. Two in your personal ledger -file: - -@smallexample -2004/09/29 Circuit City - Assets:Reimbursements:Company XYZ $100.00 - Liabilities:MasterCard $-100.00 - -2004/10/15 Company XYZ - Assets:Checking $100.00 - Assets:Reimbursements:Company XYZ $-100.00 -@end smallexample - -And two in your company ledger file: - -@smallexample -!account Company XYZ - -2004/09/29 Circuit City - Expenses:Computer:Software $100.00 - Accounts Payable:Your Name $-100.00 - -2004/10/15 Company XYZ - Accounts Payable:Your Name $100.00 - Assets:Checking $-100.00 - -!end -@end smallexample - -(Note: The @samp{!account} above means that all accounts mentioned in -the file are children of that account. In this case it means that all -activity in the file relates to Company XYZ). - -After creating these entries, you will always know that $100.00 was -spent using your MasterCard on behalf of Company XYZ, and that Company -XYZ spent the money on computer software and paid it back about two -weeks later. - -@node Commodities and Currencies, Accounts and Inventories, Assets and Liabilities, Keeping a ledger -@section Commodities and Currencies - -Ledger makes no assumptions about the commodities you use; it only -requires that you specify a commodity. The commodity may be any -non-numeric string that does not contain a period, comma, forward -slash or at-sign. It may appear before or after the amount, although -it is assumed that symbols appearing before the amount refer to -currencies, while non-joined symbols appearing after the amount refer -to commodities. Here are some valid currency and commodity -specifiers: - -@example -$20.00 ; currency: twenty US dollars -40 AAPL ; commodity: 40 shares of Apple stock -60 DM ; currency: 60 Deutsch Mark -£50 ; currency: 50 British pounds -50 EUR ; currency: 50 Euros (or use appropriate symbol) -@end example - -Ledger will examine the first use of any commodity to determine how -that commodity should be printed on reports. It pays attention to -whether the name of commodity was separated from the amount, whether -it came before or after, the precision used in specifying the amount, -whether thousand marks were used, etc. This is done so that printing -the commodity looks the same as the way you use it. - -An account may contain multiple commodities, in which case it will -have separate totals for each. For example, if your brokerage account -contains both cash, gold, and several stock quantities, the balance -might look like: - -@smallexample - $200.00 -100.00 AU - AAPL 40 - BORL 100 - FEQTX 50 Assets:Brokerage -@end smallexample - -This balance report shows how much of each commodity is in your -brokerage account. - -Sometimes, you will want to know the current street value of your -balance, and not the commodity totals. For this to happen, you must -specify what the current price is for each commodity. The price can -be any commodity, in which case the balance will be computed in terms -of that commodity. The usual way to specify prices is with a price -history file, which might look like this: - -@smallexample -P 2004/06/21 02:18:01 FEQTX $22.49 -P 2004/06/21 02:18:01 BORL $6.20 -P 2004/06/21 02:18:02 AAPL $32.91 -P 2004/06/21 02:18:02 AU $400.00 -@end smallexample - -Specify the price history to use with the @option{--price-db} option, -with the @option{-V} option to report in terms of current market -value: - -@example -ledger --price-db prices.db -V balance brokerage -@end example - -The balance for your brokerage account will be reported in US dollars, -since the prices database uses that currency. - -@smallexample -$40880.00 Assets:Brokerage -@end smallexample - -You can convert from any commodity to any other commodity. Let's say -you had $5000 in your checking account, and for whatever reason you -wanted to know many ounces of gold that would buy, in terms of the -current price of gold: - -@example -ledger -T "@{1 AU@}*(O/P@{1 AU@})" balance checking -@end example - -Although the total expression appears complex, it is simply saying -that the reported total should be in multiples of AU units, where the -quantity is the account total divided by the price of one AU. Without -the initial multiplication, the reported total would still use the -dollars commodity, since multiplying or dividing amounts always keeps -the left value's commodity. The result of this command might be: - -@smallexample -14.01 AU Assets:Checking -@end smallexample - -@subsection Commodity price histories - -Whenever a commodity is purchased using a different commodity (such as -a share of common stock using dollars), it establishes a price for -that commodity on that day. It is also possible, by recording price -details in a ledger file, to specify other prices for commodities at -any given time. Such price entries might look like those below: - -@smallexample -P 2004/06/21 02:17:58 TWCUX $27.76 -P 2004/06/21 02:17:59 AGTHX $25.41 -P 2004/06/21 02:18:00 OPTFX $39.31 -P 2004/06/21 02:18:01 FEQTX $22.49 -P 2004/06/21 02:18:02 AAPL $32.91 -@end smallexample - -By default, ledger will not consider commodity prices when generating -its various reports. It will always report balances in terms of the -commodity total, rather than the current value of those commodities. -To enable pricing reports, use one of the commodity reporting options. - -@subsection Commodity equivalencies - -Sometimes a commodity has several forms which are all equivalent. An -example of this is time. Whether tracked in terms of minutes, hours -or days, it should be possible to convert between the various forms. -Doing this requires the use of commodity equivalencies. - -For example, you might have the following two transactions, one which -transfers an hour of time into a @samp{Billable} account, and another -which decreases the same account by ten minutes. The resulting report -will indicate that fifty minutes remain: - -@smallexample -2005/10/01 Work done for company - Billable:Client 1h - Project:XYZ - -2005/10/02 Return ten minutes to the project - Project:XYZ 10m - Billable:Client -@end smallexample - -Reporting the balance for this ledger file produces: - -@smallexample - 50.0m Billable:Client - -50.0m Project:XYZ -@end smallexample - -This example works because ledger already knows how to handle seconds, -minutes and hours, as part of its time tracking support. Defining -other equivalencies is simple. The following is an example that -creates data equivalencies, helpful for tracking bytes, kilobytes, -megabytes, and more: - -@smallexample -C 1.00 Kb = 1024 b -C 1.00 Mb = 1024 Kb -C 1.00 Gb = 1024 Mb -C 1.00 Tb = 1024 Gb -@end smallexample - -Each of these definitions correlates a commodity (such as @samp{Kb}) -and a default precision, with a certain quantity of another commodity. -In the above example, kilobytes are reporetd with two decimal places -of precision and each kilobyte is equal to 1024 bytes. - -Equivalency chains can be as long as desired. Whenever a commodity -would report as a decimal amount (less than @samp{1.00}), the next -smallest commodity is used. If a commodity could be reported in terms -of a higher commodity without resulting to a partial fraction, then -the larger commodity is used. - -@node Accounts and Inventories, Understanding Equity, Commodities and Currencies, Keeping a ledger -@section Accounts and Inventories - -Since Ledger's accounts and commodity system is so flexible, you can -have accounts that don't really exist, and use commodities that no one -else recognizes. For example, let's say you are buying and selling -various items in EverQuest, and want to keep track of them using a -ledger. Just add items of whatever quantity you wish into your -EverQuest account: - -@smallexample -9/29 Get some stuff at the Inn - Places:Black's Tavern -3 Apples - Places:Black's Tavern -5 Steaks - EverQuest:Inventory -@end smallexample - -Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The -amounts are negative, because you are taking @emph{from} Black's -Tavern in order to add to your Inventory account. Note that you don't -have to use @samp{Places:Black's Tavern} as the source account. You -could use @samp{EverQuest:System} to represent the fact that you -acquired them online. The only purpose for choosing one kind of -source account over another is for generate more informative reports -later on. The more you know, the better analysis you can perform. - -If you later sell some of these items to another player, the entry -would look like: - -@smallexample -10/2 Sturm Brightblade - EverQuest:Inventory -2 Steaks - EverQuest:Inventory 15 Gold -@end smallexample - -Now you've turned 2 steaks into 15 gold, courtesy of your customer, -Sturm Brightblade. - -@node Understanding Equity, Dealing with Petty Cash, Accounts and Inventories, Keeping a ledger -@section Understanding Equity - -The most confusing entry in any ledger will be your equity account--- -because starting balances can't come out of nowhere. - -When you first start your ledger, you will likely already have money -in some of your accounts. Let's say there's $100 in your checking -account; then add an entry to your ledger to reflect this amount. -Where will money come from? The answer: your equity. - -@smallexample -10/2 Opening Balance - Assets:Checking $100.00 - Equity:Opening Balances -@end smallexample - -But what is equity? You may have heard of equity when people talked -about house mortgages, as ``the part of the house that you own''. -Basically, equity is like the value of something. If you own a car -worth $5000, then you have $5000 in equity in that car. In order to -turn that car (a commodity) into a cash flow, or a credit to your bank -account, you will have to debit the equity by selling it. - -When you start a ledger, you are probably already worth something. -Your net worth is your current equity. By transferring the money in -the ledger from your equity to your bank accounts, you are crediting -the ledger account based on your prior equity. That is why, when you -look at the balance report, you will see a large negative number for -Equity that never changes: Because that is what you were worth (what -you debited from yourself in order to start the ledger) before the -money started moving around. If the total positive value of your -assets is greater than the absolute value of your starting equity, it -means you are making money. - -Clear as mud? Keep thinking about it. Until you figure it out, put -@samp{-Equity} at the end of your balance command, to remove the -confusing figure from the total. - -@node Dealing with Petty Cash, Working with multiple funds and accounts, Understanding Equity, Keeping a ledger -@section Dealing with Petty Cash - -Something that stops many people from keeping a ledger at all is the -insanity of tracking small cash expenses. They rarely generate a -receipt, and there are often a lot of small transactions, rather than -a few large ones, as with checks. - -One solution is: don't bother. Move your spending to a debit card, -but in general ignore cash. Once you withdraw it from the ATM, mark -it as already spent to an @samp{Expenses:Cash} category: - -@smallexample -2004/03/15 ATM - Expenses:Cash $100.00 - Assets:Checking -@end smallexample - -If at some point you make a large cash expense that you want to track, -just ``move'' the amount of the expense from @samp{Expenses:Cash} into -the target account: - -@smallexample -2004/03/20 Somebody - Expenses:Food $65.00 - Expenses:Cash -@end smallexample - -This way, you can still track large cash expenses, while ignoring all -of the smaller ones. - -@node Working with multiple funds and accounts, Archiving previous years, Dealing with Petty Cash, Keeping a ledger -@section Working with multiple funds and accounts - -There are situations when the accounts you're tracking are different -between your clients and the financial institutions where money is -kept. An example of this is working as the treasurer for a religious -institution. From the secular point of view, you might be working -with three different accounts: - -@itemize -@item Checking -@item Savings -@item Credit Card -@end itemize - -From a religious point of view, the community expects to divide its -resources into multiple ``funds'', from which it makes purchases or -reserves resources for later: - -@itemize -@item School fund -@item Building fund -@item Community fund -@end itemize - -The problem with this kind of setup is that when you spend money, it -comes from two or more places at once: the account and the fund. And -yet, the correlation of amounts between funds and accounts is rarely -one-to-one. What if the school fund has @samp{$500.00}, but -@samp{$400.00} of that comes from Checking, and @samp{$100.00} from -Savings? - -Traditional finance packages require that the money reside in only one -place. But there are really two ``views'' of the data: from the -account point of view and from the fund point of view -- yet both sets -should reflect the same overall expenses and cash flow. It's simply -where the money resides that differs. - -This situation can be handled one of two ways. The first is using -virtual transactions to represent the fact that money is moving to and -from two kind of accounts at the same time: - -@smallexample -2004/03/20 Contributions - Assets:Checking $500.00 - Income:Donations - -2004/03/25 Distribution of donations - [Funds:School] $300.00 - [Funds:Building] $200.00 - [Assets:Checking] $-500.00 -@end smallexample - -The use of square brackets in the second entry ensures that the -virtual transactions balance to zero. Now money can be spent directly -from a fund at the same time as money is drawn from a physical -account: - -@smallexample -2004/03/25 Payment for books (paid from Checking) - Expenses:Books $100.00 - Assets:Checking $-100.00 - (Funds:School) $-100.00 -@end smallexample - -When reports are generated, by default they'll appear in terms of the -funds. In this case, you will likely want to mask out your -@samp{Assets} account, because otherwise the balance won't make much -sense: - -@example -ledger bal -^Assets -@end example - -If the @option{--real} option is used, the report will be in terms of -the real accounts: - -@example -ledger --real bal -@end example - -If more asset accounts are needed as the source of a transaction, just -list them as you would normally, for example: - -@smallexample -2004/03/25 Payment for books (paid from Checking) - Expenses:Books $100.00 - Assets:Checking $-50.00 - Liabilities:Credit Card $-50.00 - (Funds:School) $-100.00 -@end smallexample - -The second way of tracking funds is to use entry codes. In this -respect the codes become like virtual accounts that embrace the entire -set of transactions. Basically, we are associating an entry with a -fund by setting its code. Here are two entries that desposit money -into, and spend money from, the @samp{Funds:School} fund: - -@smallexample -2004/03/25 (Funds:School) Donations - Assets:Checking $100.00 - Income:Donations - -2004/04/25 (Funds:School) Payment for books - Expenses:Books $50.00 - Assets:Checking -@end smallexample - -Note how the accounts now relate only to the real accounts, and any -balance or registers reports will reflect this. That the entries -relate to a particular fund is kept only in the code. - -How does this become a fund report? By using the -@option{--code-as-payee} option, you can generate a register report -where the payee for each transaction shows the code. Alone, this is -not terribly interesting; but when combined with the -@option{--by-payee} option, you will now see account subtotals for any -transactions related to a specific fund. So, to see the current -monetary balances of all funds, the command would be: - -@smallexample -ledger --code-as-payee -P reg ^Assets -@end smallexample - -Or to see a particular funds expenses, the @samp{School} fund in this -case: - -@smallexample -ledger --code-as-payee -P reg ^Expenses -- School -@end smallexample - -Both approaches yield different kinds of flexibility, depending on how -you prefer to think of your funds: as virtual accounts, or as tags -associated with particular entries. Your own tastes will decide which -is best for your situation. - -@node Archiving previous years, Virtual transactions, Working with multiple funds and accounts, Keeping a ledger -@section Archiving previous years - -After a while, your ledger can get to be pretty large. While this -will not slow down the ledger program much---it's designed to process -ledger files very quickly---things can start to feel ``messy''; and -it's a universal complaint that when finances feel messy, people avoid -them. - -Thus, archiving the data from previous years into their own files can -offer a sense of completion, and freedom from the past. But how to -best accomplish this with the ledger program? There are two commands -that make it very simple: @command{print}, and @command{equity}. - -Let's take an example file, with data ranging from year 2000 until -2004. We want to archive years 2000 and 2001 to their own file, -leaving just 2003 and 2004 in the current file. So, use -@command{print} to output all the earlier entries to a file called -@file{ledger-old.dat}: - -@smallexample -ledger -f ledger.dat -b 2000 -e 2001 print > ledger-old.dat -@end smallexample - -To delete older data from the current ledger file, use @command{print} -again, this time specifying year 2002 as the starting date: - -@example -ledger -f ledger.dat -b 2002 print > x -mv x ledger.dat -@end example - -However, now the current file contains @emph{only} transactions from -2002 onward, which will not yield accurate present-day balances, -because the net income from previous years is no longer being tallied. -To compensate for this, we must append an equity report for the old -ledger at the beginning of the new one: - -@example -ledger -f ledger-old.dat equity > equity.dat -cat equity.dat ledger.dat > x -mv x ledger.dat -rm equity.dat -@end example - -Now the balances reported from @file{ledger.dat} are identical to what -they were before the data was split. - -How often should you split your ledger? You never need to, if you -don't want to. Even eighty years of data will not slow down ledger -much---and that's just using present day hardware! Or, you can keep -the previous and current year in one file, and each year before that -in its own file. It's really up to you, and how you want to organize -your finances. For those who also keep an accurate paper trail, it -might be useful to archive the older years to their own files, then -burn those files to a CD to keep with the paper records---along with -any electronic statements received during the year. In the arena of -organization, just keep in mind this maxim: Do whatever keeps you -doing it. - -@node Virtual transactions, Automated transactions, Archiving previous years, Keeping a ledger -@section Virtual transactions - -A virtual transaction is when you, in your mind, see money as moving -to a certain place, when in reality that money has not moved at all. -There are several scenarios in which this type of tracking comes in -handy, and each of them will be discussed in detail. - -To enter a virtual transaction, surround the account name in -parentheses. This form of usage does not need to balance. However, -if you want to ensure the virtual transaction balances with other -virtual transactions in the same entry, use square brackets. For -example: - -@smallexample -10/2 Paycheck - Assets:Checking $1000.00 - Income:Salary $-1000.00 - (Debt:Alimony) $200.00 -@end smallexample - -In this example, after receiving a paycheck an alimony debt is -increased---even though no money has moved around yet. - -@smallexample -10/2 Paycheck - Assets:Checking $1000.00 - Income:Salary $-1000.00 - [Savings:Trip] $200.00 - [Assets:Checking] $-200.00 -@end smallexample - -In this example, $200 has been deducted from checking toward savings -for a trip. It will appear as though the money has been moved from -the account into @samp{Savings:Trip}, although no money has actually -moved anywhere. - -When balances are displayed, virtual transactions will be factored in. -To view balances without any virtual balances factored in, using the -@option{-R} flag, for ``reality''. - -@node Automated transactions, Using Emacs to Keep Your Ledger, Virtual transactions, Keeping a ledger -@section Automated transactions - -As a Bahá'í, I need to compute Huqúqu'lláh whenever I acquire assets. -It is similar to tithing for Jews and Christians, or to Zakát for -Muslims. The exact details of computing Huqúqu'lláh are somewhat -complex, but if you have further interest, please consult the Web. - -Ledger makes this otherwise difficult law very easy. Just set up an -automated transaction at the top of your ledger file: - -@smallexample -; This automated entry will compute Huqúqu'lláh based on this -; journal's transactions. Any that match will affect the -; Liabilities:Huququ'llah account by 19% of the value of that -; transaction. - -= /^(?:Income:|Expenses:(?:Business|Rent$|Furnishings|Taxes|Insurance))/ - (Liabilities:Huququ'llah) 0.19 -@end smallexample - -This automated transaction works by looking at each transaction in the -ledger file. If any match the given value expression, 19% of the -transaction's value is applied to the @samp{Liabilities:Huququ'llah} -account. So, if $1000 is earned from @samp{Income:Salary}, $190 is -added to @samp{Liabilities:Huqúqu'lláh}; if $1000 is spent on Rent, -$190 is subtracted. The ultimate balance of Huqúqu'lláh reflects how -much is owed in order to fulfill one's obligation to Huqúqu'lláh. -When ready to pay, just write a check to cover the amount shown in -@samp{Liabilities:Huququ'llah}. That entry would look like: - -@smallexample -2003/01/01 (101) Baha'i Huqúqu'lláh Trust - Liabilities:Huququ'llah $1,000.00 - Assets:Checking -@end smallexample - -That's it. To see how much Huqúq is currently owed based on your -ledger entries, use: - -@example -ledger balance Liabilities:Huquq -@end example - -This works fine, but omits one aspect of the law: that Huquq is only -due once the liability exceeds the value of 19 mithqáls of gold (which -is roughly 2.22 ounces). So what we want is for the liability to -appear in the balance report only when it exceeds the present day -value of 2.22 ounces of gold. This can be accomplished using the -command: - -@smallexample -ledger -Q -t "/Liab.*Huquq/?(a/P@{2.22 AU@}<=@{-1.0@}&a):a" -s bal liab -@end smallexample - -With this command, the current price for gold is downloaded, and the -Huqúqu'lláh is reported only if its value exceeds that of 2.22 ounces -of gold. If you wish the liability to be reflected in the parent -subtotal either way, use this instead: - -@smallexample -ledger -Q -T "/Liab.*Huquq/?(O/P@{2.22 AU@}<=@{-1.0@}&O):O" -s bal liab -@end smallexample - -In some cases, you may wish to refer to the account of whichever -transaction matched your automated entry's value expression. To do -this, use the special account name @samp{$account}: - -@smallexample -= /^Some:Long:Account:Name/ - [$account] -0.10 - [Savings] 0.10 -@end smallexample - -This example causes 10% of the matching account's total to be deferred -to the @samp{Savings} account---as a balanced virtual transaction, -which may be excluded from reports by using @option{--real}. - -@node Using Emacs to Keep Your Ledger, Using GnuCash to Keep Your Ledger, Automated transactions, Keeping a ledger -@section Using Emacs to Keep Your Ledger - -In the Ledger tarball is an Emacs module, @file{ledger.el}. This -module makes the process of keeping a text ledger much easier for -Emacs users. I recommend putting this at the top of your ledger file: - -@example -; -*-ledger-*- -@end example - -And this in your @file{.emacs} file, after copying @file{ledger.el} to -your @file{site-lisp} directory: - -@example -(load "ledger") -@end example - -Now when you edit your ledger file, it will be in -@command{ledger-mode}. @command{ledger-mode} adds these commands: - -@table @strong -@item C-c C-a -For quickly adding new entries based on the form of older ones (see -previous section). - -@item C-c C-c -Toggles the ``cleared'' flag of the transaction under point. - -@item C-c C-d -Delete the entry under point. - -@item C-c C-r -Reconciles an account by displaying the transactions in another -buffer, where simply hitting the spacebar will toggle the pending flag -of the transaction in the ledger. Once all the appropriate -transactions have been marked, press C-c C-c in the reconcile buffer -to ``commit'' the reconciliation, which will mark all of the entries -as cleared, and display the new cleared balance in the minibuffer. - -@item C-c C-m -Set the default month for new entries added with C-c C-a. This is -handy if you have a large number of transactions to enter from a -previous month. - -@item C-c C-y -Set the default year for new entries added with C-c C-a. This is -handy if you have a large number of transactions to enter from a -previous year. -@end table - -Once you enter the reconcile buffer, there are several key commands -available: - -@table @strong -@item RET -Visit the ledger file entry corresponding to the reconcile entry. - -@item C-c C-c -Commit the reconcialation. This marks all of the marked transactions -as ``cleared'', saves the ledger file, and then displays the new -cleared balance. - -@item C-l -Refresh the reconcile buffer by re-reading transactions from the -ledger data file. - -@item SPC -Toggle the transaction under point as cleared. - -@item a -Add a new entry to the ledger data file, and refresh the reconcile -buffer to include its transactions (if the entry is added to the same -account as the one being reconciled). - -@item d -Delete the entry related to the transaction under point. Note: This -may result in multiple transactions being deleted. - -@item n -Move to the next line. - -@item p -Move to the previous line. - -@item C-c C-r -@item r -Attempt to auto-reconcile the transactions to the entered balance. If -it can do so, it will mark all those transactions as pending that -would yield the specified balance. - -@item C-x C-s -@item s -Save the ledger data file, and show the current cleared balance for -the account being reconciled. - -@item q -Quit the reconcile buffer. -@end table - -There is also an @command{emacs} command which can be used to output -reports in a format directly @code{read}-able from Emacs Lisp. - -@node Using GnuCash to Keep Your Ledger, Using timeclock to record billable time, Using Emacs to Keep Your Ledger, Keeping a ledger -@section Using GnuCash to Keep Your Ledger - -The Ledger tool is fast and simple, but it offers no custom method for -actually editing the ledger. It assumes you know how to use a text -editor, and like doing so. Perhaps an Emacs mode will appear someday -soon to make editing Ledger's data files much easier. - -Until then, you are free to use GnuCash to maintain your ledger, and -the Ledger program for querying and reporting on the contents of that -ledger. It takes a little longer to parse the XML data format that -GnuCash uses, but the end result is identical. - -Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth -to edit their data, and a one megabyte binary to query it? - -@node Using timeclock to record billable time, , Using GnuCash to Keep Your Ledger, Keeping a ledger -@section Using timeclock to record billable time - -The timeclock tool makes it easy to track time events, like clocking -into and out of a particular job. These events accumulate in a -timelog file. - -Each in/out event may have an optional description. If the ``in'' -description is a ledger account name, these in/out pairs may be viewed -as virtual transactions, adding time commodities (hours) to that -account. - -For example, the command-line version of the timeclock tool could be -used to begin a timelog file like: - -@example -export TIMELOG=$HOME/.timelog -ti ClientOne category -sleep 10 -to waited for ten seconds -@end example - -The @file{.timelog} file now contains: - -@smallexample -i 2004/10/06 15:21:00 ClientOne category -o 2004/10/06 15:21:10 waited for ten seconds -@end smallexample - -Ledger parses this directly, as if it had seen the following entry: - -@smallexample -2004/10/06 category - (ClientOne) 10s -@end smallexample - -In other words, the timelog event pair is seen as adding 0.00277h (ten -seconds) worth of time to the @samp{ClientOne} account. This would be -considered billable time, which later could be invoiced and credited -to accounts receivable: - -@smallexample -2004/11/01 (INV#1) ClientOne, Inc. - Receivable:ClientOne $0.10 - ClientOne -0.00277h @@ $35.00 -@end smallexample - -The above transaction converts the clocked time into an invoice for -the time spent, at an hourly rate of $35. Once the invoice is paid, -the money is deposited from the receivable account into a checking -account: - -@smallexample -2004/12/01 ClientOne, Inc. - Assets:Checking $0.10 - Receivable:ClientOne -@end smallexample - -And now the time spent has been turned into hard cash in the checking -account. - -The advantage to using timeclock and invoicing to bill time is that -you will always know, by looking at the balance report, exactly how -much unbilled and unpaid time you've spent working for any particular -client. - -I like to @samp{!include} my timelog at the top of my company's -accounting ledger, with the attached prefix @samp{Billable}: - -@smallexample -; -*-ledger-*- - -; This is the ledger file for my company. But first, include the -; timelog data, entering all of the time events within the umbrella -; account "Billable". - -!account Billable -!include /home/johnw/.timelog -!end - -; Here follows this fiscal year's transactions for the company. - -2004/11/01 (INV#1) ClientOne, Inc. - Receivable:ClientOne $0.10 - Billable:ClientOne -0.00277h @@ $35.00 - -2004/12/01 ClientOne, Inc. - Assets:Checking $0.10 - Receivable:ClientOne -@end smallexample - -@node Using XML, , Keeping a ledger, Top -@chapter Using XML - -By default, Ledger uses a human-readable data format, and displays its -reports in a manner meant to be read on screen. For the purpose of -writing tools which use Ledger, however, it is possible to read and -display data using XML. This chapter documents that format. - -The general format used for Ledger data is: - -@smallexample - - - ... - ... - ...... - -@end smallexample - -The data stream is enclosed in a @samp{ledger} tag, which contains a -series of one or more entries. Each @samp{entry} describes the entry -and contains a series of one or more transactions: - -@smallexample - - 2004/03/01 - - 100 - John Wiegley - - ... - ... - ...... - - -@end smallexample - -The date format for @samp{en:date} is always @samp{YYYY/MM/DD}. The -@samp{en:cleared} tag is optional, and indicates whether the -transaction has been cleared or not. There is also an -@samp{en:pending} tag, for marking pending transactions. The -@samp{en:code} and @samp{en:payee} tags both contain whatever text the -user wishes. - -After the initial entry data, there must follow a set of transactions -marked with @samp{en:transactions}. Typically these transactions will -all balance each other, but if not they will be automatically balanced -into an account named @samp{}. - -Within the @samp{en:transactions} tag is a series of one or more -@samp{transaction}'s, which have the following form: - -@smallexample - - Expenses:Computer:Hardware - - - - $ - 90.00 - - - - -@end smallexample - -This is a basic transaction. It may also be begin with -@samp{tr:virtual} and/or @samp{tr:generated} tags, to indicate virtual -and auto-generated transactions. Then follows the @samp{tr:account} -tag, which contains the full name of the account the transaction is -related to. Colons separate parent from child in an account name. - -Lastly follows the amount of the transaction, indicated by -@samp{tr:amount}. Within this tag is a @samp{value} tag, of which -there are four different kinds, each with its own format: - -@enumerate -@item boolean -@item integer -@item amount -@item balance -@end enumerate - -The format of a boolean value is @samp{true} or @samp{false} -surrounded by a @samp{boolean} tag, for example: - -@smallexample -true -@end smallexample - -The format of an integer value is the numerical value surrounded by an -@samp{integer} tag, for example: - -@smallexample -12036 -@end smallexample - -The format of an amount contains two members, the commodity and the -quantity. The commodity can have a set of flags that indicate how to -display it. The meaning of the flags (all of which are optional) are: - -@table @strong -@item P -The commodity is prefixed to the value. -@item S -The commodity is separated from the value by a space. -@item T -Thousands markers are used to display the amount. -@item E -The format of the amount is European, with period used as a thousands -marker, and comma used as the decimal point. -@end table - -The actual quantity for an amount is an integer of arbitrary size. -Ledger uses the GNU multi-precision math library to handle such -values. The XML format assumes the reader to be equally capable. -Here is an example amount: - -@smallexample - - - $ - 90.00 - - -@end smallexample - -Lastly, a balance value contains a series of amounts, each with a -different commodity. Unlike the name, such a value does need to -balance. It is called a balance because it sums several amounts. For -example: - -@smallexample - - - - $ - 90.00 - - - DM - 200.00 - - - -@end smallexample - -That is the extent of the XML data format used by Ledger. It will -output such data if the @command{xml} command is used, and can read -the same data as long as the @file{expat} library was available -when Ledger was built. - -@bye diff --git a/ledger.vim b/ledger.vim deleted file mode 100644 index df63feb8..00000000 --- a/ledger.vim +++ /dev/null @@ -1,46 +0,0 @@ -" Vim syntax file -" filetype: ledger -" Version: 0.0.2 -" by Wolfgang Oertl; Use according to the terms of the GPL>=2. -" Revision history -" 2005-02-05 first version (partly copied from ledger.vim 0.0.1) - -if version < 600 - syntax clear -elseif exists("b:current_sytax") - finish -endif - -" for debugging -syntax clear - -" region: a normal transaction -syn region transNorm start=/^\d/ skip=/^\s/ end=/^/ fold keepend transparent contains=transDate -syn match transDate /^\d\S\+/ contained -syn match Comment /^;.*$/ -" highlight default link transNorm Question -highlight default link Comment SpecialKey -highlight default link transDate Question - -" folding: how to represent a transaction in one line. -function! MyFoldText() - let line = strpart(getline(v:foldstart), 0, 65) - " get the amount at the end of the second line - let line2 = getline(v:foldstart+1) - let pos = match(line2, "[0-9.]*$") - let line2 = strpart(line2, pos) - let pad_len = 80 - strlen(line) - strlen(line2) - if (pad_len < 0) then - pad_len = 0 - endif - let pad = strpart(" ", 0, pad_len) - return line . pad . line2 -endfunction -set foldtext=MyFoldText() -set foldmethod=syntax - -" syncinc is easy: search for the first transaction. -syn sync clear -syn sync match ledgerSync grouphere transNorm "^\d" - -let b:current_syntax = "ledger" diff --git a/ledger.xcodeproj/johnw.mode1 b/ledger.xcodeproj/johnw.mode1 deleted file mode 100644 index 0c179deb..00000000 --- a/ledger.xcodeproj/johnw.mode1 +++ /dev/null @@ -1,1434 +0,0 @@ - - - - - ActivePerspectiveName - Project - AllowedModules - - - BundleLoadPath - - MaxInstances - n - Module - PBXSmartGroupTreeModule - Name - Groups and Files Outline View - - - BundleLoadPath - - MaxInstances - n - Module - PBXNavigatorGroup - Name - Editor - - - BundleLoadPath - - MaxInstances - n - Module - XCTaskListModule - Name - Task List - - - BundleLoadPath - - MaxInstances - n - Module - XCDetailModule - Name - File and Smart Group Detail Viewer - - - BundleLoadPath - - MaxInstances - 1 - Module - PBXBuildResultsModule - Name - Detailed Build Results Viewer - - - BundleLoadPath - - MaxInstances - 1 - Module - PBXProjectFindModule - Name - Project Batch Find Tool - - - BundleLoadPath - - MaxInstances - n - Module - PBXRunSessionModule - Name - Run Log - - - BundleLoadPath - - MaxInstances - n - Module - PBXBookmarksModule - Name - Bookmarks Tool - - - BundleLoadPath - - MaxInstances - n - Module - PBXClassBrowserModule - Name - Class Browser - - - BundleLoadPath - - MaxInstances - n - Module - PBXCVSModule - Name - Source Code Control Tool - - - BundleLoadPath - - MaxInstances - n - Module - PBXDebugBreakpointsModule - Name - Debug Breakpoints Tool - - - BundleLoadPath - - MaxInstances - n - Module - XCDockableInspector - Name - Inspector - - - BundleLoadPath - - MaxInstances - n - Module - PBXOpenQuicklyModule - Name - Open Quickly Tool - - - BundleLoadPath - - MaxInstances - 1 - Module - PBXDebugSessionModule - Name - Debugger - - - BundleLoadPath - - MaxInstances - 1 - Module - PBXDebugCLIModule - Name - Debug Console - - - Description - DefaultDescriptionKey - DockingSystemVisible - - Extension - mode1 - FavBarConfig - - PBXProjectModuleGUID - 33AD83720B8027C500CF4200 - XCBarModuleItemNames - - XCBarModuleItems - - - FirstTimeWindowDisplayed - - Identifier - com.apple.perspectives.project.mode1 - MajorVersion - 31 - MinorVersion - 1 - Name - Default - Notifications - - - XCObserverAutoDisconnectKey - - XCObserverDefintionKey - - PBXStatusErrorsKey - 0 - - XCObserverFactoryKey - XCPerspectivesSpecificationIdentifier - XCObserverGUIDKey - XCObserverProjectIdentifier - XCObserverNotificationKey - PBXStatusBuildStateMessageNotification - XCObserverTargetKey - XCMainBuildResultsModuleGUID - XCObserverTriggerKey - awakenModuleWithObserver: - XCObserverValidationKey - - PBXStatusErrorsKey - 2 - - - - XCObserverAutoDisconnectKey - - XCObserverDefintionKey - - PBXStatusWarningsKey - 0 - - XCObserverFactoryKey - XCPerspectivesSpecificationIdentifier - XCObserverGUIDKey - XCObserverProjectIdentifier - XCObserverNotificationKey - PBXStatusBuildStateMessageNotification - XCObserverTargetKey - XCMainBuildResultsModuleGUID - XCObserverTriggerKey - awakenModuleWithObserver: - XCObserverValidationKey - - PBXStatusWarningsKey - 2 - - - - OpenEditors - - PerspectiveWidths - - -1 - -1 - - Perspectives - - - ChosenToolbarItems - - active-target-popup - action - NSToolbarFlexibleSpaceItem - buildOrClean - build-and-runOrDebug - com.apple.ide.PBXToolbarStopButton - get-info - toggle-editor - NSToolbarFlexibleSpaceItem - com.apple.pbx.toolbar.searchfield - - ControllerClassBaseName - - IconName - WindowOfProjectWithEditor - Identifier - perspective.project - IsVertical - - Layout - - - BecomeActive - - ContentConfiguration - - PBXBottomSmartGroupGIDs - - 1C37FBAC04509CD000000102 - 1C37FAAC04509CD000000102 - 1C08E77C0454961000C914BD - 1C37FABC05509CD000000102 - 1C37FABC05539CD112110102 - E2644B35053B69B200211256 - 1C37FABC04509CD000100104 - 1CC0EA4004350EF90044410B - 1CC0EA4004350EF90041110B - - PBXProjectModuleGUID - 1CE0B1FE06471DED0097A5F4 - PBXProjectModuleLabel - Files - PBXProjectStructureProvided - yes - PBXSmartGroupTreeModuleColumnData - - PBXSmartGroupTreeModuleColumnWidthsKey - - 186 - - PBXSmartGroupTreeModuleColumnsKey_v4 - - MainColumn - - - PBXSmartGroupTreeModuleOutlineStateKey_v7 - - PBXSmartGroupTreeModuleOutlineStateExpansionKey - - 08FB7794FE84155DC02AAC07 - 08FB7795FE84155DC02AAC07 - 3332304B0B802B5500C403F5 - 333230630B802BB200C403F5 - 3332304F0B802B6500C403F5 - 333230590B802B8E00C403F5 - C6859E8C029090F304C91782 - 33B8460F0BD0A60100472F4E - 1AB674ADFE9D54B511CA2CBB - 1C37FBAC04509CD000000102 - 1C37FAAC04509CD000000102 - 1C37FABC05509CD000000102 - - PBXSmartGroupTreeModuleOutlineStateSelectionKey - - - 0 - - - PBXSmartGroupTreeModuleOutlineStateVisibleRectKey - {{0, 0}, {186, 764}} - - PBXTopSmartGroupGIDs - - XCIncludePerspectivesSwitch - - XCSharingToken - com.apple.Xcode.GFSharingToken - - GeometryConfiguration - - Frame - {{0, 0}, {203, 782}} - GroupTreeTableConfiguration - - MainColumn - 186 - - RubberWindowFrame - 206 55 1041 823 0 0 1440 878 - - Module - PBXSmartGroupTreeModule - Proportion - 203pt - - - Dock - - - ContentConfiguration - - PBXProjectModuleGUID - 1CE0B20306471E060097A5F4 - PBXProjectModuleLabel - amount.cc - PBXSplitModuleInNavigatorKey - - Split0 - - PBXProjectModuleGUID - 1CE0B20406471E060097A5F4 - PBXProjectModuleLabel - amount.cc - _historyCapacity - 0 - bookmark - 3357D0BB0BD4A651004B3223 - history - - 333230340B802B2C00C403F5 - 333230760B802C3300C403F5 - 3332307B0B802C4100C403F5 - 3332308E0B802C7A00C403F5 - 333230960B802C9A00C403F5 - 333230A30B802D4000C403F5 - 333230A40B802D4000C403F5 - 333230A70B802D4000C403F5 - 333230A80B802D4000C403F5 - 333230A90B802D4000C403F5 - 333230AA0B802D4000C403F5 - 333230AB0B802D4000C403F5 - 333230AC0B802D4000C403F5 - 333230AD0B802D4000C403F5 - 333230AF0B802D4000C403F5 - 333231000B802FF000C403F5 - 33B8460B0BD0A5CC00472F4E - 33B846130BD0A63200472F4E - 33B846400BD0A6EB00472F4E - 3357D0B80BD4A651004B3223 - 3357D0B90BD4A651004B3223 - - prevStack - - 333230360B802B2C00C403F5 - 333230700B802C1B00C403F5 - 333230740B802C2700C403F5 - 333230780B802C3300C403F5 - 3332307D0B802C4100C403F5 - 3332307E0B802C4100C403F5 - 333230820B802C4D00C403F5 - 333230860B802C6100C403F5 - 3332308B0B802C7100C403F5 - 3332308C0B802C7100C403F5 - 333230900B802C7A00C403F5 - 333230940B802C8B00C403F5 - 333230990B802C9A00C403F5 - 3332309A0B802C9A00C403F5 - 333230B20B802D4000C403F5 - 333230B40B802D4000C403F5 - 333230BA0B802D4000C403F5 - 333230BE0B802D4000C403F5 - 333230C00B802D4000C403F5 - 333230C20B802D4000C403F5 - 33B8460D0BD0A5CC00472F4E - 3357D0BA0BD4A651004B3223 - - - SplitCount - 1 - - StatusBarVisibility - - - GeometryConfiguration - - Frame - {{0, 0}, {833, 544}} - RubberWindowFrame - 206 55 1041 823 0 0 1440 878 - - Module - PBXNavigatorGroup - Proportion - 544pt - - - ContentConfiguration - - PBXProjectModuleGUID - 1CE0B20506471E060097A5F4 - PBXProjectModuleLabel - Detail - - GeometryConfiguration - - Frame - {{0, 549}, {833, 233}} - RubberWindowFrame - 206 55 1041 823 0 0 1440 878 - - Module - XCDetailModule - Proportion - 233pt - - - Proportion - 833pt - - - Name - Project - ServiceClasses - - XCModuleDock - PBXSmartGroupTreeModule - XCModuleDock - PBXNavigatorGroup - XCDetailModule - - TableOfContents - - 3357D0950BD4A3DB004B3223 - 1CE0B1FE06471DED0097A5F4 - 3357D0960BD4A3DB004B3223 - 1CE0B20306471E060097A5F4 - 1CE0B20506471E060097A5F4 - - ToolbarConfiguration - xcode.toolbar.config.default - - - ControllerClassBaseName - - IconName - WindowOfProject - Identifier - perspective.morph - IsVertical - 0 - Layout - - - BecomeActive - 1 - ContentConfiguration - - PBXBottomSmartGroupGIDs - - 1C37FBAC04509CD000000102 - 1C37FAAC04509CD000000102 - 1C08E77C0454961000C914BD - 1C37FABC05509CD000000102 - 1C37FABC05539CD112110102 - E2644B35053B69B200211256 - 1C37FABC04509CD000100104 - 1CC0EA4004350EF90044410B - 1CC0EA4004350EF90041110B - - PBXProjectModuleGUID - 11E0B1FE06471DED0097A5F4 - PBXProjectModuleLabel - Files - PBXProjectStructureProvided - yes - PBXSmartGroupTreeModuleColumnData - - PBXSmartGroupTreeModuleColumnWidthsKey - - 186 - - PBXSmartGroupTreeModuleColumnsKey_v4 - - MainColumn - - - PBXSmartGroupTreeModuleOutlineStateKey_v7 - - PBXSmartGroupTreeModuleOutlineStateExpansionKey - - 29B97314FDCFA39411CA2CEA - 1C37FABC05509CD000000102 - - PBXSmartGroupTreeModuleOutlineStateSelectionKey - - - 0 - - - PBXSmartGroupTreeModuleOutlineStateVisibleRectKey - {{0, 0}, {186, 337}} - - PBXTopSmartGroupGIDs - - XCIncludePerspectivesSwitch - 1 - XCSharingToken - com.apple.Xcode.GFSharingToken - - GeometryConfiguration - - Frame - {{0, 0}, {203, 355}} - GroupTreeTableConfiguration - - MainColumn - 186 - - RubberWindowFrame - 373 269 690 397 0 0 1440 878 - - Module - PBXSmartGroupTreeModule - Proportion - 100% - - - Name - Morph - PreferredWidth - 300 - ServiceClasses - - XCModuleDock - PBXSmartGroupTreeModule - - TableOfContents - - 11E0B1FE06471DED0097A5F4 - - ToolbarConfiguration - xcode.toolbar.config.default.short - - - PerspectivesBarVisible - - ShelfIsVisible - - SourceDescription - file at '/System/Library/PrivateFrameworks/DevToolsInterface.framework/Versions/A/Resources/XCPerspectivesSpecificationMode1.xcperspec' - StatusbarIsVisible - - TimeStamp - 198485585.74654299 - ToolbarDisplayMode - 1 - ToolbarIsVisible - - ToolbarSizeMode - 1 - Type - Perspectives - UpdateMessage - The Default Workspace in this version of Xcode now includes support to hide and show the detail view (what has been referred to as the "Metro-Morph" feature). You must discard your current Default Workspace settings and update to the latest Default Workspace in order to gain this feature. Do you wish to update to the latest Workspace defaults for project '%@'? - WindowJustification - 5 - WindowOrderList - - /Volumes/Users/johnw/Projects/local/ledger/trunk/ledger.xcodeproj - 33AD83730B8027C500CF4200 - - WindowString - 206 55 1041 823 0 0 1440 878 - WindowTools - - - FirstTimeWindowDisplayed - - Identifier - windowTool.build - IsVertical - - Layout - - - Dock - - - ContentConfiguration - - PBXProjectModuleGUID - 1CD0528F0623707200166675 - PBXProjectModuleLabel - amount.cc - StatusBarVisibility - - - GeometryConfiguration - - Frame - {{0, 0}, {730, 268}} - RubberWindowFrame - 397 238 730 550 0 0 1440 878 - - Module - PBXNavigatorGroup - Proportion - 268pt - - - BecomeActive - - ContentConfiguration - - PBXBuildLogShowsTranscriptDefaultKey - {{0, 93}, {730, 143}} - PBXProjectModuleGUID - XCMainBuildResultsModuleGUID - PBXProjectModuleLabel - Build - XCBuildResultsTrigger_Collapse - 1022 - XCBuildResultsTrigger_Open - 1013 - - GeometryConfiguration - - Frame - {{0, 273}, {730, 236}} - RubberWindowFrame - 397 238 730 550 0 0 1440 878 - - Module - PBXBuildResultsModule - Proportion - 236pt - - - Proportion - 509pt - - - Name - Build Results - ServiceClasses - - PBXBuildResultsModule - - StatusbarIsVisible - - TableOfContents - - 33AD83730B8027C500CF4200 - 3357D0990BD4A3E5004B3223 - 1CD0528F0623707200166675 - XCMainBuildResultsModuleGUID - - ToolbarConfiguration - xcode.toolbar.config.build - WindowString - 397 238 730 550 0 0 1440 878 - WindowToolGUID - 33AD83730B8027C500CF4200 - WindowToolIsVisible - - - - FirstTimeWindowDisplayed - - Identifier - windowTool.debugger - IsVertical - - Layout - - - Dock - - - ContentConfiguration - - Debugger - - HorizontalSplitView - - _collapsingFrameDimension - 0.0 - _indexOfCollapsedView - 0 - _percentageOfCollapsedView - 0.0 - isCollapsed - yes - sizes - - {{0, 0}, {377, 331}} - {{377, 0}, {489, 331}} - - - VerticalSplitView - - _collapsingFrameDimension - 0.0 - _indexOfCollapsedView - 0 - _percentageOfCollapsedView - 0.0 - isCollapsed - yes - sizes - - {{0, 0}, {866, 331}} - {{0, 331}, {866, 416}} - - - - LauncherConfigVersion - 8 - PBXProjectModuleGUID - 1C162984064C10D400B95A72 - PBXProjectModuleLabel - Debug - GLUTExamples (Underwater) - - GeometryConfiguration - - DebugConsoleDrawerSize - {100, 120} - DebugConsoleVisible - None - DebugConsoleWindowFrame - {{200, 200}, {500, 300}} - DebugSTDIOWindowFrame - {{200, 200}, {500, 300}} - Frame - {{0, 0}, {866, 747}} - RubberWindowFrame - 106 71 866 788 0 0 1440 878 - - Module - PBXDebugSessionModule - Proportion - 747pt - - - Proportion - 747pt - - - Name - Debugger - ServiceClasses - - PBXDebugSessionModule - - StatusbarIsVisible - - TableOfContents - - 1CD10A99069EF8BA00B06720 - 3332303A0B802B2C00C403F5 - 1C162984064C10D400B95A72 - 3332303B0B802B2C00C403F5 - 3332303C0B802B2C00C403F5 - 3332303D0B802B2C00C403F5 - 3332303E0B802B2C00C403F5 - 3332303F0B802B2C00C403F5 - 333230400B802B2C00C403F5 - - ToolbarConfiguration - xcode.toolbar.config.debug - WindowString - 106 71 866 788 0 0 1440 878 - WindowToolGUID - 1CD10A99069EF8BA00B06720 - WindowToolIsVisible - - - - FirstTimeWindowDisplayed - - Identifier - windowTool.find - IsVertical - - Layout - - - Dock - - - Dock - - - ContentConfiguration - - PBXProjectModuleGUID - 1CDD528C0622207200134675 - PBXProjectModuleLabel - - StatusBarVisibility - - - GeometryConfiguration - - Frame - {{0, 0}, {781, 212}} - RubberWindowFrame - 227 385 781 470 0 0 1440 878 - - Module - PBXNavigatorGroup - Proportion - 781pt - - - Proportion - 212pt - - - ContentConfiguration - - PBXProjectModuleGUID - 1CD0528E0623707200166675 - PBXProjectModuleLabel - Project Find - - GeometryConfiguration - - Frame - {{0, 217}, {781, 212}} - RubberWindowFrame - 227 385 781 470 0 0 1440 878 - - Module - PBXProjectFindModule - Proportion - 212pt - - - Proportion - 429pt - - - Name - Project Find - ServiceClasses - - PBXProjectFindModule - - StatusbarIsVisible - - TableOfContents - - 1C530D57069F1CE1000CFCEE - 3332309E0B802CA500C403F5 - 3332309F0B802CA500C403F5 - 1CDD528C0622207200134675 - 1CD0528E0623707200166675 - - WindowString - 227 385 781 470 0 0 1440 878 - WindowToolGUID - 1C530D57069F1CE1000CFCEE - WindowToolIsVisible - - - - Identifier - MENUSEPARATOR - - - FirstTimeWindowDisplayed - - Identifier - windowTool.debuggerConsole - IsVertical - - Layout - - - Dock - - - BecomeActive - - ContentConfiguration - - PBXProjectModuleGUID - 1C78EAAC065D492600B07095 - PBXProjectModuleLabel - Debugger Console - - GeometryConfiguration - - Frame - {{0, 0}, {440, 358}} - RubberWindowFrame - 127 436 440 400 0 0 1440 878 - - Module - PBXDebugCLIModule - Proportion - 358pt - - - Proportion - 359pt - - - Name - Debugger Console - ServiceClasses - - PBXDebugCLIModule - - StatusbarIsVisible - - TableOfContents - - 333230D00B802DB800C403F5 - 333230D10B802DB800C403F5 - 1C78EAAC065D492600B07095 - - WindowString - 127 436 440 400 0 0 1440 878 - WindowToolGUID - 333230D00B802DB800C403F5 - WindowToolIsVisible - - - - Identifier - windowTool.run - Layout - - - Dock - - - ContentConfiguration - - LauncherConfigVersion - 3 - PBXProjectModuleGUID - 1CD0528B0623707200166675 - PBXProjectModuleLabel - Run - Runner - - HorizontalSplitView - - _collapsingFrameDimension - 0.0 - _indexOfCollapsedView - 0 - _percentageOfCollapsedView - 0.0 - isCollapsed - yes - sizes - - {{0, 0}, {493, 167}} - {{0, 176}, {493, 267}} - - - VerticalSplitView - - _collapsingFrameDimension - 0.0 - _indexOfCollapsedView - 0 - _percentageOfCollapsedView - 0.0 - isCollapsed - yes - sizes - - {{0, 0}, {405, 443}} - {{414, 0}, {514, 443}} - - - - - GeometryConfiguration - - Frame - {{0, 0}, {460, 159}} - RubberWindowFrame - 316 696 459 200 0 0 1280 1002 - - Module - PBXRunSessionModule - Proportion - 159pt - - - Proportion - 159pt - - - Name - Run Log - ServiceClasses - - PBXRunSessionModule - - StatusbarIsVisible - 1 - TableOfContents - - 1C0AD2B3069F1EA900FABCE6 - 1C0AD2B4069F1EA900FABCE6 - 1CD0528B0623707200166675 - 1C0AD2B5069F1EA900FABCE6 - - ToolbarConfiguration - xcode.toolbar.config.run - WindowString - 316 696 459 200 0 0 1280 1002 - WindowToolGUID - 1C0AD2B3069F1EA900FABCE6 - WindowToolIsVisible - 0 - - - FirstTimeWindowDisplayed - - Identifier - windowTool.scm - IsVertical - - Layout - - - Dock - - - ContentConfiguration - - PBXProjectModuleGUID - 1C78EAB2065D492600B07095 - PBXProjectModuleLabel - - StatusBarVisibility - - - GeometryConfiguration - - Frame - {{0, 0}, {452, 0}} - RubberWindowFrame - 227 547 452 308 0 0 1440 878 - - Module - PBXNavigatorGroup - Proportion - 0pt - - - BecomeActive - - ContentConfiguration - - PBXProjectModuleGUID - 1CD052920623707200166675 - PBXProjectModuleLabel - SCM Results - - GeometryConfiguration - - Frame - {{0, 5}, {452, 262}} - RubberWindowFrame - 227 547 452 308 0 0 1440 878 - - Module - PBXCVSModule - Proportion - 262pt - - - Proportion - 267pt - - - Name - SCM - ServiceClasses - - PBXCVSModule - - StatusbarIsVisible - - TableOfContents - - 333230D30B802DD900C403F5 - 333230D40B802DD900C403F5 - 1C78EAB2065D492600B07095 - 1CD052920623707200166675 - - ToolbarConfiguration - xcode.toolbar.config.scm - WindowString - 227 547 452 308 0 0 1440 878 - WindowToolGUID - 333230D30B802DD900C403F5 - WindowToolIsVisible - - - - FirstTimeWindowDisplayed - - Identifier - windowTool.breakpoints - IsVertical - - Layout - - - Dock - - - ContentConfiguration - - PBXBottomSmartGroupGIDs - - 1C77FABC04509CD000000102 - - PBXProjectModuleGUID - 1CE0B1FE06471DED0097A5F4 - PBXProjectModuleLabel - Files - PBXProjectStructureProvided - no - PBXSmartGroupTreeModuleColumnData - - PBXSmartGroupTreeModuleColumnWidthsKey - - 168 - - PBXSmartGroupTreeModuleColumnsKey_v4 - - MainColumn - - - PBXSmartGroupTreeModuleOutlineStateKey_v7 - - PBXSmartGroupTreeModuleOutlineStateExpansionKey - - 1C77FABC04509CD000000102 - - PBXSmartGroupTreeModuleOutlineStateSelectionKey - - - 0 - - - PBXSmartGroupTreeModuleOutlineStateVisibleRectKey - {{0, 0}, {168, 350}} - - PBXTopSmartGroupGIDs - - XCIncludePerspectivesSwitch - - - GeometryConfiguration - - Frame - {{0, 0}, {185, 368}} - GroupTreeTableConfiguration - - MainColumn - 168 - - RubberWindowFrame - 127 427 744 409 0 0 1440 878 - - Module - PBXSmartGroupTreeModule - Proportion - 185pt - - - BecomeActive - - ContentConfiguration - - PBXProjectModuleGUID - 1CA1AED706398EBD00589147 - PBXProjectModuleLabel - Detail - - GeometryConfiguration - - Frame - {{190, 0}, {554, 368}} - RubberWindowFrame - 127 427 744 409 0 0 1440 878 - - Module - XCDetailModule - Proportion - 554pt - - - Proportion - 368pt - - - MajorVersion - 2 - MinorVersion - 0 - Name - Breakpoints - ServiceClasses - - PBXSmartGroupTreeModule - XCDetailModule - - StatusbarIsVisible - - TableOfContents - - 333230F90B802FDD00C403F5 - 333230FA0B802FDD00C403F5 - 1CE0B1FE06471DED0097A5F4 - 1CA1AED706398EBD00589147 - - ToolbarConfiguration - xcode.toolbar.config.breakpoints - WindowString - 127 427 744 409 0 0 1440 878 - WindowToolGUID - 333230F90B802FDD00C403F5 - WindowToolIsVisible - - - - Identifier - windowTool.debugAnimator - Layout - - - Dock - - - Module - PBXNavigatorGroup - Proportion - 100% - - - Proportion - 100% - - - Name - Debug Visualizer - ServiceClasses - - PBXNavigatorGroup - - StatusbarIsVisible - 1 - ToolbarConfiguration - xcode.toolbar.config.debugAnimator - WindowString - 100 100 700 500 0 0 1280 1002 - - - Identifier - windowTool.bookmarks - Layout - - - Dock - - - Module - PBXBookmarksModule - Proportion - 100% - - - Proportion - 100% - - - Name - Bookmarks - ServiceClasses - - PBXBookmarksModule - - StatusbarIsVisible - 0 - WindowString - 538 42 401 187 0 0 1280 1002 - - - FirstTimeWindowDisplayed - - Identifier - windowTool.classBrowser - IsVertical - - Layout - - - Dock - - - ContentConfiguration - - OptionsSetName - Hierarchy, project classes - PBXProjectModuleGUID - 1CA6456E063B45B4001379D8 - PBXProjectModuleLabel - Class Browser - value_t - - GeometryConfiguration - - ClassesFrame - {{0, 0}, {378, 96}} - ClassesTreeTableConfiguration - - PBXClassNameColumnIdentifier - 208 - PBXClassBookColumnIdentifier - 22 - - Frame - {{0, 0}, {630, 332}} - MembersFrame - {{0, 101}, {378, 231}} - MembersTreeTableConfiguration - - PBXMemberTypeIconColumnIdentifier - 22 - PBXMemberNameColumnIdentifier - 216 - PBXMemberTypeColumnIdentifier - 101 - PBXMemberBookColumnIdentifier - 22 - - RubberWindowFrame - 227 503 630 352 0 0 1440 878 - - Module - PBXClassBrowserModule - Proportion - 332pt - - - Proportion - 332pt - - - Name - Class Browser - ServiceClasses - - PBXClassBrowserModule - - StatusbarIsVisible - - TableOfContents - - 1C0AD2AF069F1E9B00FABCE6 - 333230E20B802E8300C403F5 - 1CA6456E063B45B4001379D8 - - ToolbarConfiguration - xcode.toolbar.config.classbrowser - WindowString - 227 503 630 352 0 0 1440 878 - WindowToolGUID - 1C0AD2AF069F1E9B00FABCE6 - WindowToolIsVisible - - - - - diff --git a/ledger.xcodeproj/johnw.pbxuser b/ledger.xcodeproj/johnw.pbxuser deleted file mode 100644 index d3c3754b..00000000 --- a/ledger.xcodeproj/johnw.pbxuser +++ /dev/null @@ -1,861 +0,0 @@ -// !$*UTF8*$! -{ - 08FB7793FE84155DC02AAC07 /* Project object */ = { - activeBuildConfigurationName = Debug; - activeExecutable = 33AD82D60B80262200CF4200 /* ledger */; - activeTarget = 8DD76F620486A84900D96B5E /* ledger */; - addToTargets = ( - 8DD76F620486A84900D96B5E /* ledger */, - ); - breakpoints = ( - 333230A20B802D3E00C403F5 /* xpath.h:768 */, - ); - breakpointsGroup = 333231030B802FF000C403F5 /* XCBreakpointsBucket */; - codeSenseManager = 33AD82DB0B80264000CF4200 /* Code sense */; - executables = ( - 33AD82D60B80262200CF4200 /* ledger */, - ); - perUserDictionary = { - "PBXConfiguration.PBXBreakpointsDataSource.v1:1CA1AED706398EBD00589147" = { - PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; - PBXFileTableDataSourceColumnSortingKey = PBXBreakpointsDataSource_BreakpointID; - PBXFileTableDataSourceColumnWidthsKey = ( - 20, - 20, - 210, - 20, - 110, - 109, - 20, - ); - PBXFileTableDataSourceColumnsKey = ( - PBXBreakpointsDataSource_ActionID, - PBXBreakpointsDataSource_TypeID, - PBXBreakpointsDataSource_BreakpointID, - PBXBreakpointsDataSource_UseID, - PBXBreakpointsDataSource_LocationID, - PBXBreakpointsDataSource_ConditionID, - PBXBreakpointsDataSource_ContinueID, - ); - }; - PBXConfiguration.PBXFileTableDataSource3.PBXExecutablesDataSource = { - PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; - PBXFileTableDataSourceColumnSortingKey = PBXExecutablesDataSource_NameID; - PBXFileTableDataSourceColumnWidthsKey = ( - 22, - 300, - 481.5835, - ); - PBXFileTableDataSourceColumnsKey = ( - PBXExecutablesDataSource_ActiveFlagID, - PBXExecutablesDataSource_NameID, - PBXExecutablesDataSource_CommentsID, - ); - }; - PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { - PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; - PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; - PBXFileTableDataSourceColumnWidthsKey = ( - 20, - 594, - 20, - 48, - 43, - 43, - 20, - ); - PBXFileTableDataSourceColumnsKey = ( - PBXFileDataSource_FiletypeID, - PBXFileDataSource_Filename_ColumnID, - PBXFileDataSource_Built_ColumnID, - PBXFileDataSource_ObjectSize_ColumnID, - PBXFileDataSource_Errors_ColumnID, - PBXFileDataSource_Warnings_ColumnID, - PBXFileDataSource_Target_ColumnID, - ); - }; - PBXConfiguration.PBXTargetDataSource.PBXTargetDataSource = { - PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; - PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; - PBXFileTableDataSourceColumnWidthsKey = ( - 20, - 200, - 63, - 20, - 48, - 43, - 43, - ); - PBXFileTableDataSourceColumnsKey = ( - PBXFileDataSource_FiletypeID, - PBXFileDataSource_Filename_ColumnID, - PBXTargetDataSource_PrimaryAttribute, - PBXFileDataSource_Built_ColumnID, - PBXFileDataSource_ObjectSize_ColumnID, - PBXFileDataSource_Errors_ColumnID, - PBXFileDataSource_Warnings_ColumnID, - ); - }; - PBXPerProjectTemplateStateSaveDate = 198484953; - PBXWorkspaceStateSaveDate = 198484953; - }; - perUserProjectItems = { - 333230340B802B2C00C403F5 /* PBXTextBookmark */ = 333230340B802B2C00C403F5 /* PBXTextBookmark */; - 333230360B802B2C00C403F5 /* PBXTextBookmark */ = 333230360B802B2C00C403F5 /* PBXTextBookmark */; - 333230700B802C1B00C403F5 /* PBXTextBookmark */ = 333230700B802C1B00C403F5 /* PBXTextBookmark */; - 333230740B802C2700C403F5 /* PBXTextBookmark */ = 333230740B802C2700C403F5 /* PBXTextBookmark */; - 333230760B802C3300C403F5 /* PBXTextBookmark */ = 333230760B802C3300C403F5 /* PBXTextBookmark */; - 333230780B802C3300C403F5 /* PBXTextBookmark */ = 333230780B802C3300C403F5 /* PBXTextBookmark */; - 3332307B0B802C4100C403F5 /* PBXTextBookmark */ = 3332307B0B802C4100C403F5 /* PBXTextBookmark */; - 3332307D0B802C4100C403F5 /* PBXTextBookmark */ = 3332307D0B802C4100C403F5 /* PBXTextBookmark */; - 3332307E0B802C4100C403F5 /* PBXTextBookmark */ = 3332307E0B802C4100C403F5 /* PBXTextBookmark */; - 333230820B802C4D00C403F5 /* PBXTextBookmark */ = 333230820B802C4D00C403F5 /* PBXTextBookmark */; - 333230860B802C6100C403F5 /* PBXTextBookmark */ = 333230860B802C6100C403F5 /* PBXTextBookmark */; - 3332308B0B802C7100C403F5 /* PBXTextBookmark */ = 3332308B0B802C7100C403F5 /* PBXTextBookmark */; - 3332308C0B802C7100C403F5 /* PBXTextBookmark */ = 3332308C0B802C7100C403F5 /* PBXTextBookmark */; - 3332308E0B802C7A00C403F5 /* PBXTextBookmark */ = 3332308E0B802C7A00C403F5 /* PBXTextBookmark */; - 333230900B802C7A00C403F5 /* PBXTextBookmark */ = 333230900B802C7A00C403F5 /* PBXTextBookmark */; - 333230940B802C8B00C403F5 /* PBXTextBookmark */ = 333230940B802C8B00C403F5 /* PBXTextBookmark */; - 333230960B802C9A00C403F5 /* PBXTextBookmark */ = 333230960B802C9A00C403F5 /* PBXTextBookmark */; - 333230990B802C9A00C403F5 /* PBXTextBookmark */ = 333230990B802C9A00C403F5 /* PBXTextBookmark */; - 3332309A0B802C9A00C403F5 /* PBXTextBookmark */ = 3332309A0B802C9A00C403F5 /* PBXTextBookmark */; - 333230A30B802D4000C403F5 /* PBXTextBookmark */ = 333230A30B802D4000C403F5 /* PBXTextBookmark */; - 333230A40B802D4000C403F5 /* PBXTextBookmark */ = 333230A40B802D4000C403F5 /* PBXTextBookmark */; - 333230A70B802D4000C403F5 /* PBXTextBookmark */ = 333230A70B802D4000C403F5 /* PBXTextBookmark */; - 333230A80B802D4000C403F5 /* PBXTextBookmark */ = 333230A80B802D4000C403F5 /* PBXTextBookmark */; - 333230A90B802D4000C403F5 /* PBXTextBookmark */ = 333230A90B802D4000C403F5 /* PBXTextBookmark */; - 333230AA0B802D4000C403F5 /* PBXTextBookmark */ = 333230AA0B802D4000C403F5 /* PBXTextBookmark */; - 333230AB0B802D4000C403F5 /* PBXTextBookmark */ = 333230AB0B802D4000C403F5 /* PBXTextBookmark */; - 333230AC0B802D4000C403F5 /* PBXTextBookmark */ = 333230AC0B802D4000C403F5 /* PBXTextBookmark */; - 333230AD0B802D4000C403F5 /* PBXTextBookmark */ = 333230AD0B802D4000C403F5 /* PBXTextBookmark */; - 333230AF0B802D4000C403F5 /* PBXTextBookmark */ = 333230AF0B802D4000C403F5 /* PBXTextBookmark */; - 333230B20B802D4000C403F5 /* PBXTextBookmark */ = 333230B20B802D4000C403F5 /* PBXTextBookmark */; - 333230B40B802D4000C403F5 /* PBXTextBookmark */ = 333230B40B802D4000C403F5 /* PBXTextBookmark */; - 333230BA0B802D4000C403F5 /* PBXTextBookmark */ = 333230BA0B802D4000C403F5 /* PBXTextBookmark */; - 333230BE0B802D4000C403F5 /* PBXTextBookmark */ = 333230BE0B802D4000C403F5 /* PBXTextBookmark */; - 333230C00B802D4000C403F5 /* PBXTextBookmark */ = 333230C00B802D4000C403F5 /* PBXTextBookmark */; - 333230C20B802D4000C403F5 /* PBXTextBookmark */ = 333230C20B802D4000C403F5 /* PBXTextBookmark */; - 333231000B802FF000C403F5 /* PBXTextBookmark */ = 333231000B802FF000C403F5 /* PBXTextBookmark */; - 3357D0B80BD4A651004B3223 /* PBXTextBookmark */ = 3357D0B80BD4A651004B3223 /* PBXTextBookmark */; - 3357D0B90BD4A651004B3223 /* PBXTextBookmark */ = 3357D0B90BD4A651004B3223 /* PBXTextBookmark */; - 3357D0BA0BD4A651004B3223 /* PBXTextBookmark */ = 3357D0BA0BD4A651004B3223 /* PBXTextBookmark */; - 3357D0BB0BD4A651004B3223 /* PBXTextBookmark */ = 3357D0BB0BD4A651004B3223 /* PBXTextBookmark */; - 33B8460B0BD0A5CC00472F4E /* PBXTextBookmark */ = 33B8460B0BD0A5CC00472F4E /* PBXTextBookmark */; - 33B8460D0BD0A5CC00472F4E /* PBXTextBookmark */ = 33B8460D0BD0A5CC00472F4E /* PBXTextBookmark */; - 33B846130BD0A63200472F4E /* PBXTextBookmark */ = 33B846130BD0A63200472F4E /* PBXTextBookmark */; - 33B846400BD0A6EB00472F4E /* PBXTextBookmark */ = 33B846400BD0A6EB00472F4E /* PBXTextBookmark */; - }; - sourceControlManager = 33AD82DA0B80264000CF4200 /* Source Control */; - userBuildSettings = { - }; - }; - 333230340B802B2C00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82F00B80269C00CF4200 /* format.cc */; - name = "format.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 808; - vrLoc = 0; - }; - 333230360B802B2C00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82F00B80269C00CF4200 /* format.cc */; - name = "format.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 808; - vrLoc = 0; - }; - 333230700B802C1B00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 3356EA090B8029FA00EC228D /* option.cc */; - name = "option.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 794; - vrLoc = 0; - }; - 333230740B802C2700C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83750B80280B00CF4200 /* acconf.h */; - name = "acconf.h: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1172; - vrLoc = 0; - }; - 333230760B802C3300C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83050B80269C00CF4200 /* quotes.cc */; - name = "quotes.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1072; - vrLoc = 0; - }; - 333230780B802C3300C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83050B80269C00CF4200 /* quotes.cc */; - name = "quotes.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1072; - vrLoc = 0; - }; - 3332307B0B802C4100C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82DD0B80269C00CF4200 /* amount.h */; - name = "TRACE_CTOR(\"amount_t()\");"; - rLen = 30; - rLoc = 920; - rType = 0; - vrLen = 645; - vrLoc = 0; - }; - 3332307D0B802C4100C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; - name = "static std::time_t base = -1;"; - rLen = 39; - rLoc = 595; - rType = 0; - vrLen = 740; - vrLoc = 0; - }; - 3332307E0B802C4100C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82DD0B80269C00CF4200 /* amount.h */; - name = "TRACE_CTOR(\"amount_t()\");"; - rLen = 30; - rLoc = 920; - rType = 0; - vrLen = 645; - vrLoc = 0; - }; - 333230820B802C4D00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; - name = "amount.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 801; - vrLoc = 0; - }; - 333230860B802C6100C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82F40B80269C00CF4200 /* journal.cc */; - name = "journal.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 723; - vrLoc = 0; - }; - 3332308B0B802C7100C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E00B80269C00CF4200 /* binary.cc */; - name = "binary.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1013; - vrLoc = 0; - }; - 3332308C0B802C7100C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83180B80269C00CF4200 /* xml.cc */; - name = "xml.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 912; - vrLoc = 0; - }; - 3332308E0B802C7A00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD831A0B80269C00CF4200 /* xmlparse.cc */; - name = "xmlparse.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 946; - vrLoc = 0; - }; - 333230900B802C7A00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD831A0B80269C00CF4200 /* xmlparse.cc */; - name = "xmlparse.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 946; - vrLoc = 0; - }; - 333230940B802C8B00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83070B80269C00CF4200 /* reconcile.cc */; - name = "reconcile.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 0; - vrLoc = 0; - }; - 333230960B802C9A00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83090B80269C00CF4200 /* report.cc */; - name = "report.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1023; - vrLoc = 0; - }; - 333230990B802C9A00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83090B80269C00CF4200 /* report.cc */; - name = "report.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1023; - vrLoc = 0; - }; - 3332309A0B802C9A00C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E20B80269C00CF4200 /* csv.cc */; - name = "csv.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 0; - vrLoc = 0; - }; - 333230A20B802D3E00C403F5 /* xpath.h:768 */ = { - isa = PBXFileBreakpoint; - actions = ( - ); - breakpointStyle = 0; - continueAfterActions = 0; - delayBeforeContinue = 0; - fileReference = 33AD831D0B80269C00CF4200 /* xpath.h */; - functionName = "operator()"; - hitCount = 1; - lineNumber = 768; - location = main.ob; - modificationTime = 192950207.974497; - state = 1; - }; - 333230A30B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82EF0B80269C00CF4200 /* fdstream.hpp */; - name = "fdstream.hpp: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1154; - vrLoc = 0; - }; - 333230A40B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82F60B80269C00CF4200 /* ledger.h */; - name = "ledger.h: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 775; - vrLoc = 0; - }; - 333230A70B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82F40B80269C00CF4200 /* journal.cc */; - name = "journal.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 723; - vrLoc = 0; - }; - 333230A80B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E10B80269C00CF4200 /* binary.h */; - name = "binary.h: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 839; - vrLoc = 0; - }; - 333230A90B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E00B80269C00CF4200 /* binary.cc */; - name = "binary.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1013; - vrLoc = 0; - }; - 333230AA0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83180B80269C00CF4200 /* xml.cc */; - name = "xml.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 912; - vrLoc = 0; - }; - 333230AB0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83070B80269C00CF4200 /* reconcile.cc */; - name = "reconcile.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 0; - vrLoc = 0; - }; - 333230AC0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E80B80269C00CF4200 /* derive.cc */; - name = "derive.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 934; - vrLoc = 0; - }; - 333230AD0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E20B80269C00CF4200 /* csv.cc */; - name = "csv.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 0; - vrLoc = 0; - }; - 333230AF0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 3356EA090B8029FA00EC228D /* option.cc */; - name = "option.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 794; - vrLoc = 0; - }; - 333230B20B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82EF0B80269C00CF4200 /* fdstream.hpp */; - name = "fdstream.hpp: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 1154; - vrLoc = 0; - }; - 333230B40B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82F60B80269C00CF4200 /* ledger.h */; - name = "ledger.h: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 775; - vrLoc = 0; - }; - 333230BA0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E10B80269C00CF4200 /* binary.h */; - name = "binary.h: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 839; - vrLoc = 0; - }; - 333230BE0B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E80B80269C00CF4200 /* derive.cc */; - name = "derive.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 934; - vrLoc = 0; - }; - 333230C00B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 3356EA000B80299700EC228D /* main.cc */; - name = "main.cc: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 696; - vrLoc = 0; - }; - 333230C20B802D4000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83190B80269C00CF4200 /* xml.h */; - name = "}"; - rLen = 4; - rLoc = 1896; - rType = 0; - vrLen = 1033; - vrLoc = 1373; - }; - 333231000B802FF000C403F5 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 3356EA000B80299700EC228D /* main.cc */; - name = "ledger::tracing_active = true;"; - rLen = 35; - rLoc = 10634; - rType = 0; - vrLen = 718; - vrLoc = 10521; - }; - 333231030B802FF000C403F5 /* XCBreakpointsBucket */ = { - isa = XCBreakpointsBucket; - name = "Project Breakpoints"; - objects = ( - 333230A20B802D3E00C403F5 /* xpath.h:768 */, - ); - }; - 3356EA000B80299700EC228D /* main.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {825, 8910}}"; - sepNavSelRange = "{10634, 0}"; - sepNavVisRect = "{{0, 7305}, {825, 384}}"; - }; - }; - 3356EA090B8029FA00EC228D /* option.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {767, 5202}}"; - sepNavSelRange = "{2917, 0}"; - sepNavVisRect = "{{0, 2231}, {689, 236}}"; - }; - }; - 3357D0B80BD4A651004B3223 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; - name = "static std::time_t base = -1;"; - rLen = 32; - rLoc = 550; - rType = 0; - vrLen = 473; - vrLoc = 240; - }; - 3357D0B90BD4A651004B3223 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - comments = "warning: control reaches end of non-void function"; - fRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; - rLen = 1; - rLoc = 1226; - rType = 1; - }; - 3357D0BA0BD4A651004B3223 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; - name = "static std::time_t base = -1;"; - rLen = 32; - rLoc = 550; - rType = 0; - vrLen = 473; - vrLoc = 240; - }; - 3357D0BB0BD4A651004B3223 /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; - name = "amount.cc: 2046"; - rLen = 0; - rLoc = 51254; - rType = 0; - vrLen = 580; - vrLoc = 29938; - }; - 33AD82D60B80262200CF4200 /* ledger */ = { - isa = PBXExecutable; - activeArgIndex = 0; - activeArgIndices = ( - YES, - YES, - YES, - ); - argumentStrings = ( - "-f", - /home/johnw/doc/Finances/ledger.dat, - xml, - ); - autoAttachOnCrash = 1; - configStateDict = { - }; - customDataFormattersEnabled = 1; - debuggerPlugin = GDBDebugging; - disassemblyDisplayState = 0; - dylibVariantSuffix = ""; - enableDebugStr = 1; - environmentEntries = ( - ); - executableSystemSymbolLevel = 0; - executableUserSymbolLevel = 0; - libgmallocEnabled = 0; - name = ledger; - savedGlobals = { - }; - sourceDirectories = ( - ); - variableFormatDictionary = { - }; - }; - 33AD82DA0B80264000CF4200 /* Source Control */ = { - isa = PBXSourceControlManager; - fallbackIsa = XCSourceControlManager; - isSCMEnabled = 0; - scmConfiguration = { - SubversionToolPath = /usr/local/bin/svn; - }; - scmType = ""; - }; - 33AD82DB0B80264000CF4200 /* Code sense */ = { - isa = PBXCodeSenseManager; - indexTemplatePath = ""; - }; - 33AD82DC0B80269C00CF4200 /* amount.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {794, 36828}}"; - sepNavSelRange = "{51254, 0}"; - sepNavVisRect = "{{0, 21942}, {689, 236}}"; - }; - }; - 33AD82DD0B80269C00CF4200 /* amount.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 11178}}"; - sepNavSelRange = "{920, 30}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82DF0B80269C00CF4200 /* balance.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {686, 17244}}"; - sepNavSelRange = "{206, 16}"; - sepNavVisRect = "{{0, 0}, {337, 199}}"; - }; - }; - 33AD82E00B80269C00CF4200 /* binary.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 18378}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82E10B80269C00CF4200 /* binary.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 4662}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82E20B80269C00CF4200 /* csv.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 745}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82E40B80269C00CF4200 /* datetime.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 10368}}"; - sepNavSelRange = "{550, 32}"; - sepNavVisRect = "{{0, 219}, {792, 512}}"; - }; - }; - 33AD82E80B80269C00CF4200 /* derive.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 3294}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82EF0B80269C00CF4200 /* fdstream.hpp */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 3330}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82F00B80269C00CF4200 /* format.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 4770}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD82F20B80269C00CF4200 /* gnucash.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {749, 6912}}"; - sepNavSelRange = "{11616, 0}"; - sepNavVisRect = "{{0, 6522}, {459, 186}}"; - }; - }; - 33AD82F40B80269C00CF4200 /* journal.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {776, 18828}}"; - sepNavSelRange = "{14713, 0}"; - sepNavVisRect = "{{41, 10857}, {459, 186}}"; - }; - }; - 33AD82F60B80269C00CF4200 /* ledger.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 846}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD83050B80269C00CF4200 /* quotes.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 1566}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD83070B80269C00CF4200 /* reconcile.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 745}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD83090B80269C00CF4200 /* report.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 3852}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 745}}"; - }; - }; - 33AD830D0B80269C00CF4200 /* textual.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {830, 16146}}"; - sepNavSelRange = "{13898, 0}"; - sepNavVisRect = "{{0, 10086}, {459, 186}}"; - }; - }; - 33AD83160B80269C00CF4200 /* value.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {776, 47790}}"; - sepNavSelRange = "{51277, 0}"; - sepNavVisRect = "{{0, 33738}, {459, 186}}"; - }; - }; - 33AD83170B80269C00CF4200 /* value.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {722, 10170}}"; - sepNavSelRange = "{702, 14}"; - sepNavVisRect = "{{0, 360}, {337, 199}}"; - }; - }; - 33AD83180B80269C00CF4200 /* xml.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {812, 8442}}"; - sepNavSelRange = "{7071, 0}"; - sepNavVisRect = "{{0, 5687}, {689, 236}}"; - }; - }; - 33AD83190B80269C00CF4200 /* xml.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 6048}}"; - sepNavSelRange = "{1896, 4}"; - sepNavVisRect = "{{0, 1463}, {792, 512}}"; - }; - }; - 33AD831A0B80269C00CF4200 /* xmlparse.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {740, 8514}}"; - sepNavSelRange = "{5476, 0}"; - sepNavVisRect = "{{0, 3545}, {689, 236}}"; - }; - }; - 33AD831C0B80269C00CF4200 /* xpath.cc */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {749, 45954}}"; - sepNavSelRange = "{46916, 0}"; - sepNavVisRect = "{{0, 35124}, {459, 186}}"; - }; - }; - 33AD831D0B80269C00CF4200 /* xpath.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {785, 13968}}"; - sepNavSelRange = "{7507, 0}"; - sepNavVisRect = "{{0, 5550}, {459, 186}}"; - }; - }; - 33AD83750B80280B00CF4200 /* acconf.h */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {792, 1674}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 512}}"; - }; - }; - 33B8460B0BD0A5CC00472F4E /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD831D0B80269C00CF4200 /* xpath.h */; - name = "xpath.h: 774"; - rLen = 0; - rLoc = 18463; - rType = 0; - vrLen = 613; - vrLoc = 17535; - }; - 33B8460D0BD0A5CC00472F4E /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD831D0B80269C00CF4200 /* xpath.h */; - name = "xpath.h: 774"; - rLen = 0; - rLoc = 18463; - rType = 0; - vrLen = 613; - vrLoc = 17535; - }; - 33B846130BD0A63200472F4E /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83750B80280B00CF4200 /* acconf.h */; - name = "acconf.h: 1"; - rLen = 0; - rLoc = 0; - rType = 0; - vrLen = 840; - vrLoc = 0; - }; - 33B846400BD0A6EB00472F4E /* PBXTextBookmark */ = { - isa = PBXTextBookmark; - fRef = 33AD83190B80269C00CF4200 /* xml.h */; - name = "}"; - rLen = 4; - rLoc = 1896; - rType = 0; - vrLen = 613; - vrLoc = 1552; - }; - 8DD76F620486A84900D96B5E /* ledger */ = { - activeExec = 0; - executables = ( - 33AD82D60B80262200CF4200 /* ledger */, - ); - }; - C6859E8B029090EE04C91782 /* ledger.1 */ = { - uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {821, 1422}}"; - sepNavSelRange = "{0, 0}"; - sepNavVisRect = "{{0, 0}, {792, 512}}"; - }; - }; -} diff --git a/ledger.xcodeproj/project.pbxproj b/ledger.xcodeproj/project.pbxproj deleted file mode 100644 index 1cdfe32b..00000000 --- a/ledger.xcodeproj/project.pbxproj +++ /dev/null @@ -1,584 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 42; - objects = { - -/* Begin PBXBuildFile section */ - 3356EA010B80299700EC228D /* main.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3356EA000B80299700EC228D /* main.cc */; }; - 3356EA0A0B8029FA00EC228D /* option.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3356EA090B8029FA00EC228D /* option.cc */; }; - 3357D09C0BD4A3FD004B3223 /* libgmp.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3357D09B0BD4A3FD004B3223 /* libgmp.dylib */; }; - 3357D09E0BD4A40E004B3223 /* libexpat.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3357D09D0BD4A40E004B3223 /* libexpat.dylib */; }; - 33AD831E0B80269C00CF4200 /* amount.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82DC0B80269C00CF4200 /* amount.cc */; }; - 33AD831F0B80269C00CF4200 /* amount.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82DD0B80269C00CF4200 /* amount.h */; }; - 33AD83200B80269C00CF4200 /* balance.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82DE0B80269C00CF4200 /* balance.cc */; }; - 33AD83210B80269C00CF4200 /* balance.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82DF0B80269C00CF4200 /* balance.h */; }; - 33AD83220B80269C00CF4200 /* binary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E00B80269C00CF4200 /* binary.cc */; }; - 33AD83230B80269C00CF4200 /* binary.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E10B80269C00CF4200 /* binary.h */; }; - 33AD83240B80269C00CF4200 /* csv.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E20B80269C00CF4200 /* csv.cc */; }; - 33AD83250B80269C00CF4200 /* csv.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E30B80269C00CF4200 /* csv.h */; }; - 33AD83260B80269C00CF4200 /* datetime.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E40B80269C00CF4200 /* datetime.cc */; }; - 33AD83270B80269C00CF4200 /* datetime.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E50B80269C00CF4200 /* datetime.h */; }; - 33AD83280B80269C00CF4200 /* debug.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E60B80269C00CF4200 /* debug.cc */; }; - 33AD83290B80269C00CF4200 /* debug.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E70B80269C00CF4200 /* debug.h */; }; - 33AD832A0B80269C00CF4200 /* derive.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82E80B80269C00CF4200 /* derive.cc */; }; - 33AD832B0B80269C00CF4200 /* derive.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82E90B80269C00CF4200 /* derive.h */; }; - 33AD832E0B80269C00CF4200 /* emacs.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82EC0B80269C00CF4200 /* emacs.cc */; }; - 33AD832F0B80269C00CF4200 /* emacs.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82ED0B80269C00CF4200 /* emacs.h */; }; - 33AD83300B80269C00CF4200 /* error.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82EE0B80269C00CF4200 /* error.h */; }; - 33AD83310B80269C00CF4200 /* fdstream.hpp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82EF0B80269C00CF4200 /* fdstream.hpp */; }; - 33AD83320B80269C00CF4200 /* format.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F00B80269C00CF4200 /* format.cc */; }; - 33AD83330B80269C00CF4200 /* format.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F10B80269C00CF4200 /* format.h */; }; - 33AD83340B80269C00CF4200 /* gnucash.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F20B80269C00CF4200 /* gnucash.cc */; }; - 33AD83350B80269C00CF4200 /* gnucash.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F30B80269C00CF4200 /* gnucash.h */; }; - 33AD83360B80269C00CF4200 /* journal.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F40B80269C00CF4200 /* journal.cc */; }; - 33AD83370B80269C00CF4200 /* journal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F50B80269C00CF4200 /* journal.h */; }; - 33AD83380B80269C00CF4200 /* ledger.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F60B80269C00CF4200 /* ledger.h */; }; - 33AD83390B80269C00CF4200 /* mask.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82F70B80269C00CF4200 /* mask.cc */; }; - 33AD833A0B80269C00CF4200 /* mask.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82F80B80269C00CF4200 /* mask.h */; }; - 33AD833D0B80269C00CF4200 /* option.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82FB0B80269C00CF4200 /* option.h */; }; - 33AD833E0B80269C00CF4200 /* parser.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD82FC0B80269C00CF4200 /* parser.cc */; }; - 33AD833F0B80269C00CF4200 /* parser.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD82FD0B80269C00CF4200 /* parser.h */; }; - 33AD83450B80269C00CF4200 /* qif.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83030B80269C00CF4200 /* qif.cc */; }; - 33AD83460B80269C00CF4200 /* qif.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83040B80269C00CF4200 /* qif.h */; }; - 33AD83470B80269C00CF4200 /* quotes.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83050B80269C00CF4200 /* quotes.cc */; }; - 33AD83480B80269C00CF4200 /* quotes.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83060B80269C00CF4200 /* quotes.h */; }; - 33AD83490B80269C00CF4200 /* reconcile.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83070B80269C00CF4200 /* reconcile.cc */; }; - 33AD834A0B80269C00CF4200 /* reconcile.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83080B80269C00CF4200 /* reconcile.h */; }; - 33AD834B0B80269C00CF4200 /* report.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83090B80269C00CF4200 /* report.cc */; }; - 33AD834C0B80269C00CF4200 /* report.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830A0B80269C00CF4200 /* report.h */; }; - 33AD834D0B80269C00CF4200 /* session.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD830B0B80269C00CF4200 /* session.cc */; }; - 33AD834E0B80269C00CF4200 /* session.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830C0B80269C00CF4200 /* session.h */; }; - 33AD834F0B80269C00CF4200 /* textual.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD830D0B80269C00CF4200 /* textual.cc */; }; - 33AD83500B80269C00CF4200 /* textual.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830E0B80269C00CF4200 /* textual.h */; }; - 33AD83510B80269C00CF4200 /* timing.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD830F0B80269C00CF4200 /* timing.h */; }; - 33AD83520B80269C00CF4200 /* trace.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83100B80269C00CF4200 /* trace.cc */; }; - 33AD83530B80269C00CF4200 /* trace.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83110B80269C00CF4200 /* trace.h */; }; - 33AD83540B80269C00CF4200 /* transform.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83120B80269C00CF4200 /* transform.cc */; }; - 33AD83550B80269C00CF4200 /* transform.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83130B80269C00CF4200 /* transform.h */; }; - 33AD83560B80269C00CF4200 /* util.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83140B80269C00CF4200 /* util.cc */; }; - 33AD83570B80269C00CF4200 /* util.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83150B80269C00CF4200 /* util.h */; }; - 33AD83580B80269C00CF4200 /* value.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83160B80269C00CF4200 /* value.cc */; }; - 33AD83590B80269C00CF4200 /* value.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83170B80269C00CF4200 /* value.h */; }; - 33AD835A0B80269C00CF4200 /* xml.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD83180B80269C00CF4200 /* xml.cc */; }; - 33AD835B0B80269C00CF4200 /* xml.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83190B80269C00CF4200 /* xml.h */; }; - 33AD835C0B80269C00CF4200 /* xmlparse.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD831A0B80269C00CF4200 /* xmlparse.cc */; }; - 33AD835D0B80269C00CF4200 /* xmlparse.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD831B0B80269C00CF4200 /* xmlparse.h */; }; - 33AD835E0B80269C00CF4200 /* xpath.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33AD831C0B80269C00CF4200 /* xpath.cc */; }; - 33AD835F0B80269C00CF4200 /* xpath.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD831D0B80269C00CF4200 /* xpath.h */; }; - 33AD83760B80280B00CF4200 /* acconf.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AD83750B80280B00CF4200 /* acconf.h */; }; - 33B846080BD0A5B200472F4E /* libpcre.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 33B846060BD0A5B200472F4E /* libpcre.dylib */; }; - 8DD76F6A0486A84900D96B5E /* ledger.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859E8B029090EE04C91782 /* ledger.1 */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 8DD76F690486A84900D96B5E /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 8; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - 8DD76F6A0486A84900D96B5E /* ledger.1 in CopyFiles */, - 33AD831F0B80269C00CF4200 /* amount.h in CopyFiles */, - 33AD83210B80269C00CF4200 /* balance.h in CopyFiles */, - 33AD83230B80269C00CF4200 /* binary.h in CopyFiles */, - 33AD83250B80269C00CF4200 /* csv.h in CopyFiles */, - 33AD83270B80269C00CF4200 /* datetime.h in CopyFiles */, - 33AD83290B80269C00CF4200 /* debug.h in CopyFiles */, - 33AD832B0B80269C00CF4200 /* derive.h in CopyFiles */, - 33AD832F0B80269C00CF4200 /* emacs.h in CopyFiles */, - 33AD83300B80269C00CF4200 /* error.h in CopyFiles */, - 33AD83310B80269C00CF4200 /* fdstream.hpp in CopyFiles */, - 33AD83330B80269C00CF4200 /* format.h in CopyFiles */, - 33AD83350B80269C00CF4200 /* gnucash.h in CopyFiles */, - 33AD83370B80269C00CF4200 /* journal.h in CopyFiles */, - 33AD83380B80269C00CF4200 /* ledger.h in CopyFiles */, - 33AD833A0B80269C00CF4200 /* mask.h in CopyFiles */, - 33AD833D0B80269C00CF4200 /* option.h in CopyFiles */, - 33AD833F0B80269C00CF4200 /* parser.h in CopyFiles */, - 33AD83460B80269C00CF4200 /* qif.h in CopyFiles */, - 33AD83480B80269C00CF4200 /* quotes.h in CopyFiles */, - 33AD834A0B80269C00CF4200 /* reconcile.h in CopyFiles */, - 33AD834C0B80269C00CF4200 /* report.h in CopyFiles */, - 33AD834E0B80269C00CF4200 /* session.h in CopyFiles */, - 33AD83500B80269C00CF4200 /* textual.h in CopyFiles */, - 33AD83510B80269C00CF4200 /* timing.h in CopyFiles */, - 33AD83530B80269C00CF4200 /* trace.h in CopyFiles */, - 33AD83550B80269C00CF4200 /* transform.h in CopyFiles */, - 33AD83570B80269C00CF4200 /* util.h in CopyFiles */, - 33AD83590B80269C00CF4200 /* value.h in CopyFiles */, - 33AD835B0B80269C00CF4200 /* xml.h in CopyFiles */, - 33AD835D0B80269C00CF4200 /* xmlparse.h in CopyFiles */, - 33AD835F0B80269C00CF4200 /* xpath.h in CopyFiles */, - 33AD83760B80280B00CF4200 /* acconf.h in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 1; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 3356EA000B80299700EC228D /* main.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cc; sourceTree = ""; }; - 3356EA090B8029FA00EC228D /* option.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = option.cc; sourceTree = ""; }; - 3357D09B0BD4A3FD004B3223 /* libgmp.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libgmp.dylib; path = /sw/lib/libgmp.dylib; sourceTree = ""; }; - 3357D09D0BD4A40E004B3223 /* libexpat.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libexpat.dylib; path = /usr/local/lib/libexpat.dylib; sourceTree = ""; }; - 33AD82DC0B80269C00CF4200 /* amount.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = amount.cc; sourceTree = ""; }; - 33AD82DD0B80269C00CF4200 /* amount.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = amount.h; sourceTree = ""; }; - 33AD82DE0B80269C00CF4200 /* balance.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = balance.cc; sourceTree = ""; }; - 33AD82DF0B80269C00CF4200 /* balance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = balance.h; sourceTree = ""; }; - 33AD82E00B80269C00CF4200 /* binary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = binary.cc; sourceTree = ""; }; - 33AD82E10B80269C00CF4200 /* binary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = binary.h; sourceTree = ""; }; - 33AD82E20B80269C00CF4200 /* csv.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = csv.cc; sourceTree = ""; }; - 33AD82E30B80269C00CF4200 /* csv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = csv.h; sourceTree = ""; }; - 33AD82E40B80269C00CF4200 /* datetime.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = datetime.cc; sourceTree = ""; }; - 33AD82E50B80269C00CF4200 /* datetime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = datetime.h; sourceTree = ""; }; - 33AD82E60B80269C00CF4200 /* debug.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debug.cc; sourceTree = ""; }; - 33AD82E70B80269C00CF4200 /* debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = ""; }; - 33AD82E80B80269C00CF4200 /* derive.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = derive.cc; sourceTree = ""; }; - 33AD82E90B80269C00CF4200 /* derive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = derive.h; sourceTree = ""; }; - 33AD82EC0B80269C00CF4200 /* emacs.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emacs.cc; sourceTree = ""; }; - 33AD82ED0B80269C00CF4200 /* emacs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = emacs.h; sourceTree = ""; }; - 33AD82EE0B80269C00CF4200 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = ""; }; - 33AD82EF0B80269C00CF4200 /* fdstream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = fdstream.hpp; sourceTree = ""; }; - 33AD82F00B80269C00CF4200 /* format.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = format.cc; sourceTree = ""; }; - 33AD82F10B80269C00CF4200 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = format.h; sourceTree = ""; }; - 33AD82F20B80269C00CF4200 /* gnucash.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = gnucash.cc; sourceTree = ""; }; - 33AD82F30B80269C00CF4200 /* gnucash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gnucash.h; sourceTree = ""; }; - 33AD82F40B80269C00CF4200 /* journal.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = journal.cc; sourceTree = ""; }; - 33AD82F50B80269C00CF4200 /* journal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = journal.h; sourceTree = ""; }; - 33AD82F60B80269C00CF4200 /* ledger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ledger.h; sourceTree = ""; }; - 33AD82F70B80269C00CF4200 /* mask.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mask.cc; sourceTree = ""; }; - 33AD82F80B80269C00CF4200 /* mask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mask.h; sourceTree = ""; }; - 33AD82FB0B80269C00CF4200 /* option.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = option.h; sourceTree = ""; }; - 33AD82FC0B80269C00CF4200 /* parser.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parser.cc; sourceTree = ""; }; - 33AD82FD0B80269C00CF4200 /* parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = ""; }; - 33AD83030B80269C00CF4200 /* qif.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = qif.cc; sourceTree = ""; }; - 33AD83040B80269C00CF4200 /* qif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = qif.h; sourceTree = ""; }; - 33AD83050B80269C00CF4200 /* quotes.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = quotes.cc; sourceTree = ""; }; - 33AD83060B80269C00CF4200 /* quotes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = quotes.h; sourceTree = ""; }; - 33AD83070B80269C00CF4200 /* reconcile.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reconcile.cc; sourceTree = ""; }; - 33AD83080B80269C00CF4200 /* reconcile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reconcile.h; sourceTree = ""; }; - 33AD83090B80269C00CF4200 /* report.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = report.cc; sourceTree = ""; }; - 33AD830A0B80269C00CF4200 /* report.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = report.h; sourceTree = ""; }; - 33AD830B0B80269C00CF4200 /* session.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = session.cc; sourceTree = ""; }; - 33AD830C0B80269C00CF4200 /* session.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = session.h; sourceTree = ""; }; - 33AD830D0B80269C00CF4200 /* textual.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = textual.cc; sourceTree = ""; }; - 33AD830E0B80269C00CF4200 /* textual.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = textual.h; sourceTree = ""; }; - 33AD830F0B80269C00CF4200 /* timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timing.h; sourceTree = ""; }; - 33AD83100B80269C00CF4200 /* trace.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = trace.cc; sourceTree = ""; }; - 33AD83110B80269C00CF4200 /* trace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trace.h; sourceTree = ""; }; - 33AD83120B80269C00CF4200 /* transform.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = transform.cc; sourceTree = ""; }; - 33AD83130B80269C00CF4200 /* transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = transform.h; sourceTree = ""; }; - 33AD83140B80269C00CF4200 /* util.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = util.cc; sourceTree = ""; }; - 33AD83150B80269C00CF4200 /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = util.h; sourceTree = ""; }; - 33AD83160B80269C00CF4200 /* value.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = value.cc; sourceTree = ""; }; - 33AD83170B80269C00CF4200 /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = value.h; sourceTree = ""; }; - 33AD83180B80269C00CF4200 /* xml.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xml.cc; sourceTree = ""; }; - 33AD83190B80269C00CF4200 /* xml.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xml.h; sourceTree = ""; }; - 33AD831A0B80269C00CF4200 /* xmlparse.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xmlparse.cc; sourceTree = ""; }; - 33AD831B0B80269C00CF4200 /* xmlparse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xmlparse.h; sourceTree = ""; }; - 33AD831C0B80269C00CF4200 /* xpath.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xpath.cc; sourceTree = ""; }; - 33AD831D0B80269C00CF4200 /* xpath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xpath.h; sourceTree = ""; }; - 33AD83750B80280B00CF4200 /* acconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = acconf.h; sourceTree = ""; }; - 33B846060BD0A5B200472F4E /* libpcre.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpcre.dylib; path = /usr/local/lib/libpcre.dylib; sourceTree = ""; }; - 8DD76F6C0486A84900D96B5E /* ledger */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "compiled.mach-o.executable"; path = ledger; sourceTree = BUILT_PRODUCTS_DIR; }; - C6859E8B029090EE04C91782 /* ledger.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = ledger.1; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 8DD76F660486A84900D96B5E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 33B846080BD0A5B200472F4E /* libpcre.dylib in Frameworks */, - 3357D09C0BD4A3FD004B3223 /* libgmp.dylib in Frameworks */, - 3357D09E0BD4A40E004B3223 /* libexpat.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 08FB7794FE84155DC02AAC07 /* ledger */ = { - isa = PBXGroup; - children = ( - 08FB7795FE84155DC02AAC07 /* Source */, - C6859E8C029090F304C91782 /* Documentation */, - 33B8460F0BD0A60100472F4E /* Dependencies */, - 1AB674ADFE9D54B511CA2CBB /* Products */, - ); - name = ledger; - sourceTree = ""; - }; - 08FB7795FE84155DC02AAC07 /* Source */ = { - isa = PBXGroup; - children = ( - 333230420B802B3A00C403F5 /* Utility code */, - 333230470B802B4700C403F5 /* Core numerics */, - 3332304B0B802B5500C403F5 /* Journal representation */, - 3332304F0B802B6500C403F5 /* XML meta-representation */, - 333230530B802B7400C403F5 /* Transformations */, - 333230570B802B8200C403F5 /* Reporting */, - 333230590B802B8E00C403F5 /* Command-line driver */, - 3332305B0B802B9E00C403F5 /* Python scripting */, - ); - name = Source; - sourceTree = ""; - }; - 1AB674ADFE9D54B511CA2CBB /* Products */ = { - isa = PBXGroup; - children = ( - 8DD76F6C0486A84900D96B5E /* ledger */, - ); - name = Products; - sourceTree = ""; - }; - 333230420B802B3A00C403F5 /* Utility code */ = { - isa = PBXGroup; - children = ( - 33AD82F60B80269C00CF4200 /* ledger.h */, - 33AD83140B80269C00CF4200 /* util.cc */, - 33AD83150B80269C00CF4200 /* util.h */, - 33AD83750B80280B00CF4200 /* acconf.h */, - 33AD82E60B80269C00CF4200 /* debug.cc */, - 33AD82E70B80269C00CF4200 /* debug.h */, - 33AD82EE0B80269C00CF4200 /* error.h */, - 33AD830F0B80269C00CF4200 /* timing.h */, - 33AD83100B80269C00CF4200 /* trace.cc */, - 33AD83110B80269C00CF4200 /* trace.h */, - ); - name = "Utility code"; - sourceTree = ""; - }; - 333230470B802B4700C403F5 /* Core numerics */ = { - isa = PBXGroup; - children = ( - 3332306A0B802BC800C403F5 /* Common data types */, - 333230670B802BC800C403F5 /* Amounts and values */, - ); - name = "Core numerics"; - sourceTree = ""; - }; - 3332304B0B802B5500C403F5 /* Journal representation */ = { - isa = PBXGroup; - children = ( - 33AD82F40B80269C00CF4200 /* journal.cc */, - 33AD82F50B80269C00CF4200 /* journal.h */, - 33AD82FC0B80269C00CF4200 /* parser.cc */, - 33AD82FD0B80269C00CF4200 /* parser.h */, - 333230630B802BB200C403F5 /* Input formats */, - ); - name = "Journal representation"; - sourceTree = ""; - }; - 3332304F0B802B6500C403F5 /* XML meta-representation */ = { - isa = PBXGroup; - children = ( - 33AD83180B80269C00CF4200 /* xml.cc */, - 33AD83190B80269C00CF4200 /* xml.h */, - 33AD831C0B80269C00CF4200 /* xpath.cc */, - 33AD831D0B80269C00CF4200 /* xpath.h */, - ); - name = "XML meta-representation"; - sourceTree = ""; - }; - 333230530B802B7400C403F5 /* Transformations */ = { - isa = PBXGroup; - children = ( - 33AD83070B80269C00CF4200 /* reconcile.cc */, - 33AD83080B80269C00CF4200 /* reconcile.h */, - 33AD83120B80269C00CF4200 /* transform.cc */, - 33AD83130B80269C00CF4200 /* transform.h */, - ); - name = Transformations; - sourceTree = ""; - }; - 333230570B802B8200C403F5 /* Reporting */ = { - isa = PBXGroup; - children = ( - 33AD82E80B80269C00CF4200 /* derive.cc */, - 33AD82E90B80269C00CF4200 /* derive.h */, - 33AD82F00B80269C00CF4200 /* format.cc */, - 33AD82F10B80269C00CF4200 /* format.h */, - 33AD83090B80269C00CF4200 /* report.cc */, - 33AD830A0B80269C00CF4200 /* report.h */, - 3332305F0B802BAA00C403F5 /* Output formats */, - ); - name = Reporting; - sourceTree = ""; - }; - 333230590B802B8E00C403F5 /* Command-line driver */ = { - isa = PBXGroup; - children = ( - 3356EA000B80299700EC228D /* main.cc */, - 3356EA090B8029FA00EC228D /* option.cc */, - 33AD82FB0B80269C00CF4200 /* option.h */, - 33AD830B0B80269C00CF4200 /* session.cc */, - 33AD830C0B80269C00CF4200 /* session.h */, - 33AD82EF0B80269C00CF4200 /* fdstream.hpp */, - ); - name = "Command-line driver"; - sourceTree = ""; - }; - 3332305B0B802B9E00C403F5 /* Python scripting */ = { - isa = PBXGroup; - children = ( - ); - name = "Python scripting"; - sourceTree = ""; - }; - 3332305F0B802BAA00C403F5 /* Output formats */ = { - isa = PBXGroup; - children = ( - 33AD82E20B80269C00CF4200 /* csv.cc */, - 33AD82E30B80269C00CF4200 /* csv.h */, - 33AD82EC0B80269C00CF4200 /* emacs.cc */, - 33AD82ED0B80269C00CF4200 /* emacs.h */, - ); - name = "Output formats"; - sourceTree = ""; - }; - 333230630B802BB200C403F5 /* Input formats */ = { - isa = PBXGroup; - children = ( - 33AD82E00B80269C00CF4200 /* binary.cc */, - 33AD82E10B80269C00CF4200 /* binary.h */, - 33AD82F20B80269C00CF4200 /* gnucash.cc */, - 33AD82F30B80269C00CF4200 /* gnucash.h */, - 33AD83030B80269C00CF4200 /* qif.cc */, - 33AD83040B80269C00CF4200 /* qif.h */, - 33AD830D0B80269C00CF4200 /* textual.cc */, - 33AD830E0B80269C00CF4200 /* textual.h */, - 33AD831A0B80269C00CF4200 /* xmlparse.cc */, - 33AD831B0B80269C00CF4200 /* xmlparse.h */, - ); - name = "Input formats"; - sourceTree = ""; - }; - 333230670B802BC800C403F5 /* Amounts and values */ = { - isa = PBXGroup; - children = ( - 33AD82DC0B80269C00CF4200 /* amount.cc */, - 33AD82DD0B80269C00CF4200 /* amount.h */, - 33AD82DE0B80269C00CF4200 /* balance.cc */, - 33AD82DF0B80269C00CF4200 /* balance.h */, - 33AD83160B80269C00CF4200 /* value.cc */, - 33AD83170B80269C00CF4200 /* value.h */, - 33AD83050B80269C00CF4200 /* quotes.cc */, - 33AD83060B80269C00CF4200 /* quotes.h */, - ); - name = "Amounts and values"; - sourceTree = ""; - }; - 3332306A0B802BC800C403F5 /* Common data types */ = { - isa = PBXGroup; - children = ( - 33AD82E40B80269C00CF4200 /* datetime.cc */, - 33AD82E50B80269C00CF4200 /* datetime.h */, - 33AD82F70B80269C00CF4200 /* mask.cc */, - 33AD82F80B80269C00CF4200 /* mask.h */, - ); - name = "Common data types"; - sourceTree = ""; - }; - 33B8460F0BD0A60100472F4E /* Dependencies */ = { - isa = PBXGroup; - children = ( - 3357D09D0BD4A40E004B3223 /* libexpat.dylib */, - 3357D09B0BD4A3FD004B3223 /* libgmp.dylib */, - 33B846060BD0A5B200472F4E /* libpcre.dylib */, - ); - name = Dependencies; - sourceTree = ""; - }; - C6859E8C029090F304C91782 /* Documentation */ = { - isa = PBXGroup; - children = ( - C6859E8B029090EE04C91782 /* ledger.1 */, - ); - name = Documentation; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 8DD76F620486A84900D96B5E /* ledger */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "ledger" */; - buildPhases = ( - 8DD76F640486A84900D96B5E /* Sources */, - 8DD76F660486A84900D96B5E /* Frameworks */, - 8DD76F690486A84900D96B5E /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = ledger; - productInstallPath = "$(HOME)/bin"; - productName = ledger; - productReference = 8DD76F6C0486A84900D96B5E /* ledger */; - productType = "com.apple.product-type.tool"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 08FB7793FE84155DC02AAC07 /* Project object */ = { - isa = PBXProject; - buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "ledger" */; - hasScannedForEncodings = 1; - mainGroup = 08FB7794FE84155DC02AAC07 /* ledger */; - projectDirPath = ""; - targets = ( - 8DD76F620486A84900D96B5E /* ledger */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 8DD76F640486A84900D96B5E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33AD831E0B80269C00CF4200 /* amount.cc in Sources */, - 33AD83200B80269C00CF4200 /* balance.cc in Sources */, - 33AD83220B80269C00CF4200 /* binary.cc in Sources */, - 33AD83240B80269C00CF4200 /* csv.cc in Sources */, - 33AD83260B80269C00CF4200 /* datetime.cc in Sources */, - 33AD83280B80269C00CF4200 /* debug.cc in Sources */, - 33AD832A0B80269C00CF4200 /* derive.cc in Sources */, - 33AD832E0B80269C00CF4200 /* emacs.cc in Sources */, - 33AD83320B80269C00CF4200 /* format.cc in Sources */, - 33AD83340B80269C00CF4200 /* gnucash.cc in Sources */, - 33AD83360B80269C00CF4200 /* journal.cc in Sources */, - 33AD83390B80269C00CF4200 /* mask.cc in Sources */, - 33AD833E0B80269C00CF4200 /* parser.cc in Sources */, - 33AD83450B80269C00CF4200 /* qif.cc in Sources */, - 33AD83470B80269C00CF4200 /* quotes.cc in Sources */, - 33AD83490B80269C00CF4200 /* reconcile.cc in Sources */, - 33AD834B0B80269C00CF4200 /* report.cc in Sources */, - 33AD834D0B80269C00CF4200 /* session.cc in Sources */, - 33AD834F0B80269C00CF4200 /* textual.cc in Sources */, - 33AD83520B80269C00CF4200 /* trace.cc in Sources */, - 33AD83540B80269C00CF4200 /* transform.cc in Sources */, - 33AD83560B80269C00CF4200 /* util.cc in Sources */, - 33AD83580B80269C00CF4200 /* value.cc in Sources */, - 33AD835A0B80269C00CF4200 /* xml.cc in Sources */, - 33AD835C0B80269C00CF4200 /* xmlparse.cc in Sources */, - 33AD835E0B80269C00CF4200 /* xpath.cc in Sources */, - 3356EA010B80299700EC228D /* main.cc in Sources */, - 3356EA0A0B8029FA00EC228D /* option.cc in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 1DEB923208733DC60010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 3.0; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; - GCC_MODEL_TUNING = ""; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG_LEVEL=4", - "HAVE_EXPAT=1", - ); - HEADER_SEARCH_PATHS = /usr/local/include; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/lib, - /sw/lib, - ); - PRODUCT_NAME = ledger; - ZERO_LINK = YES; - }; - name = Debug; - }; - 1DEB923308733DC60010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = ( - ppc, - i386, - ); - CURRENT_PROJECT_VERSION = 3.0; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_MODEL_TUNING = ""; - HEADER_SEARCH_PATHS = /usr/local/include; - INSTALL_PATH = "$(HOME)/bin"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/lib, - /sw/lib, - ); - PRODUCT_NAME = ledger; - }; - name = Release; - }; - 1DEB923608733DC60010E9CD /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - /usr/local/include, - /sw/include, - ); - LIBRARY_SEARCH_PATHS = ( - /usr/local/lib, - /sw/lib, - ); - PREBINDING = NO; - SDKROOT = ""; - }; - name = Debug; - }; - 1DEB923708733DC60010E9CD /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - /usr/local/include, - /sw/include, - ); - LIBRARY_SEARCH_PATHS = ( - /usr/local/lib, - /sw/lib, - ); - PREBINDING = NO; - SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "ledger" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB923208733DC60010E9CD /* Debug */, - 1DEB923308733DC60010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "ledger" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DEB923608733DC60010E9CD /* Debug */, - 1DEB923708733DC60010E9CD /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; -} diff --git a/lisp/ledger.el b/lisp/ledger.el new file mode 100644 index 00000000..0812f8f6 --- /dev/null +++ b/lisp/ledger.el @@ -0,0 +1,1256 @@ +;;; ledger.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2007 John Wiegley (johnw AT gnu DOT org) + +;; Emacs Lisp Archive Entry +;; Filename: ledger.el +;; Version: 3.0 +;; Date: Thu 16-Apr-2007 +;; Keywords: data +;; Author: John Wiegley (johnw AT gnu DOT org) +;; Maintainer: John Wiegley (johnw AT gnu DOT org) +;; Description: Helper code for using my "ledger" command-line tool +;; URL: http://www.newartisans.com/johnw/emacs.html +;; Compatibility: Emacs22 + +;; This file is not part of GNU Emacs. + +;; This is free software; you can redistribute it and/or modify it under +;; the terms of the GNU General Public License as published by the Free +;; Software Foundation; either version 2, or (at your option) any later +;; version. +;; +;; This is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +;; for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, +;; MA 02111-1307, USA. + +;;; Commentary: + +;; To use this module: Load this file, open a ledger data file, and +;; type M-x ledger-mode. Once this is done, you can type: +;; +;; C-c C-a add a new entry, based on previous entries +;; C-c C-y set default year for entry mode +;; C-c C-m set default month for entry mode +;; C-c C-r reconcile uncleared entries related to an account +;; C-c C-o C-r run a ledger report +;; C-C C-o C-g goto the ledger report buffer +;; C-c C-o C-e edit the defined ledger reports +;; C-c C-o C-s save a report definition based on the current report +;; C-c C-o C-a rerun a ledger report +;; C-c C-o C-k kill the ledger report buffer +;; +;; In the reconcile buffer, use SPACE to toggle the cleared status of +;; a transaction, C-x C-s to save changes (to the ledger file as +;; well), or C-c C-r to attempt an auto-reconcilation based on the +;; statement's ending date and balance. +;; +;; The ledger reports command asks the user to select a report to run +;; then creates a report buffer containing the results of running the +;; associated command line. Its' behavior is modified by a prefix +;; argument which, when given, causes the generated command line that +;; will be used to create the report to be presented for editing +;; before the report is actually run. Arbitrary unnamed command lines +;; can be run by specifying an empty name for the report. The command +;; line used can later be named and saved for future use as a named +;; report from the generated reports buffer. +;; +;; In a report buffer, the following keys are available: +;; (space) scroll up +;; e edit the defined ledger reports +;; s save a report definition based on the current report +;; q quit the report (return to ledger buffer) +;; r redo the report +;; k kill the report buffer + +(require 'esh-util) +(require 'esh-arg) +(require 'pcomplete) + +(defvar ledger-version "1.2" + "The version of ledger.el currently loaded") + +(defgroup ledger nil + "Interface to the Ledger command-line accounting program." + :group 'data) + +(defcustom ledger-binary-path (executable-find "ledger") + "Path to the ledger executable." + :type 'file + :group 'ledger) + +(defcustom ledger-clear-whole-entries nil + "If non-nil, clear whole entries, not individual transactions." + :type 'boolean + :group 'ledger) + +(defcustom ledger-reports + '(("bal" "ledger -f %(ledger-file) bal") + ("reg" "ledger -f %(ledger-file) reg") + ("payee" "ledger -f %(ledger-file) reg -- %(payee)") + ("account" "ledger -f %(ledger-file) reg %(account)")) + "Definition of reports to run. + +Each element has the form (NAME CMDLINE). The command line can +contain format specifiers that are replaced with context sensitive +information. Format specifiers have the format '%()' where + is an identifier for the information to be replaced. The +`ledger-report-format-specifiers' alist variable contains a mapping +from format specifier identifier to a lisp function that implements +the substitution. See the documentation of the individual functions +in that variable for more information on the behavior of each +specifier." + :type '(repeat (list (string :tag "Report Name") + (string :tag "Command Line"))) + :group 'ledger) + +(defcustom ledger-report-format-specifiers + '(("ledger-file" . ledger-report-ledger-file-format-specifier) + ("payee" . ledger-report-payee-format-specifier) + ("account" . ledger-report-account-format-specifier)) + "Alist mapping ledger report format specifiers to implementing functions + +The function is called with no parameters and expected to return the +text that should replace the format specifier." + :type 'alist + :group 'ledger) + +(defcustom ledger-default-acct-transaction-indent " " + "Default indentation for account transactions in an entry." + :type 'string + :group 'ledger) + +(defvar bold 'bold) +(defvar ledger-font-lock-keywords + `((,(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") 3 bold) + ("^\\s-+.+?\\( \\|\t\\|\\s-+$\\)" . font-lock-keyword-face)) + "Default expressions to highlight in Ledger mode.") + +(defsubst ledger-current-year () + (format-time-string "%Y")) +(defsubst ledger-current-month () + (format-time-string "%m")) + +(defvar ledger-year (ledger-current-year) + "Start a ledger session with the current year, but make it +customizable to ease retro-entry.") +(defvar ledger-month (ledger-current-month) + "Start a ledger session with the current month, but make it +customizable to ease retro-entry.") + +(defun ledger-iterate-entries (callback) + (goto-char (point-min)) + (let* ((now (current-time)) + (current-year (nth 5 (decode-time now)))) + (while (not (eobp)) + (when (looking-at + (concat "\\(Y\\s-+\\([0-9]+\\)\\|" + "\\([0-9]\\{4\\}+\\)?[./]?" + "\\([0-9]+\\)[./]\\([0-9]+\\)\\s-+" + "\\(\\*\\s-+\\)?\\(.+\\)\\)")) + (let ((found (match-string 2))) + (if found + (setq current-year (string-to-number found)) + (let ((start (match-beginning 0)) + (year (match-string 3)) + (month (string-to-number (match-string 4))) + (day (string-to-number (match-string 5))) + (mark (match-string 6)) + (desc (match-string 7))) + (if (and year (> (length year) 0)) + (setq year (string-to-number year))) + (funcall callback start + (encode-time 0 0 0 day month + (or year current-year)) + mark desc))))) + (forward-line)))) + +(defun ledger-time-less-p (t1 t2) + "Say whether time value T1 is less than time value T2." + (or (< (car t1) (car t2)) + (and (= (car t1) (car t2)) + (< (nth 1 t1) (nth 1 t2))))) + +(defun ledger-time-subtract (t1 t2) + "Subtract two time values. +Return the difference in the format of a time value." + (let ((borrow (< (cadr t1) (cadr t2)))) + (list (- (car t1) (car t2) (if borrow 1 0)) + (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2))))) + +(defun ledger-find-slot (moment) + (catch 'found + (ledger-iterate-entries + (function + (lambda (start date mark desc) + (if (ledger-time-less-p moment date) + (throw 'found t))))))) + +(defun ledger-add-entry (entry-text) + (interactive + (list + (read-string "Entry: " (concat ledger-year "/" ledger-month "/")))) + (let* ((args (with-temp-buffer + (insert entry-text) + (eshell-parse-arguments (point-min) (point-max)))) + (date (car args)) + (insert-year t) + (ledger-buf (current-buffer)) + exit-code) + (if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date) + (setq date + (encode-time 0 0 0 (string-to-number (match-string 3 date)) + (string-to-number (match-string 2 date)) + (string-to-number (match-string 1 date))))) + (ledger-find-slot date) + (save-excursion + (if (re-search-backward "^Y " nil t) + (setq insert-year nil))) + (save-excursion + (insert + (with-temp-buffer + (setq exit-code + (apply #'ledger-run-ledger ledger-buf "entry" + (mapcar 'eval args))) + (if (= 0 exit-code) + (if insert-year + (buffer-substring 2 (point-max)) + (buffer-substring 7 (point-max))) + (concat (if insert-year entry-text + (substring entry-text 6)) "\n"))) "\n")))) + +(defun ledger-current-entry-bounds () + (save-excursion + (when (or (looking-at "^[0-9]") + (re-search-backward "^[0-9]" nil t)) + (let ((beg (point))) + (while (not (eolp)) + (forward-line)) + (cons (copy-marker beg) (point-marker)))))) + +(defun ledger-delete-current-entry () + (interactive) + (let ((bounds (ledger-current-entry-bounds))) + (delete-region (car bounds) (cdr bounds)))) + +(defun ledger-toggle-current-entry (&optional style) + (interactive) + (let (clear) + (save-excursion + (when (or (looking-at "^[0-9]") + (re-search-backward "^[0-9]" nil t)) + (skip-chars-forward "0-9./=") + (delete-horizontal-space) + (if (member (char-after) '(?\* ?\!)) + (progn + (delete-char 1) + (if (and style (eq style 'cleared)) + (insert " *"))) + (if (and style (eq style 'pending)) + (insert " ! ") + (insert " * ")) + (setq clear t)))) + clear)) + +(defun ledger-toggle-current-transaction (&optional style) + "Toggle the cleared status of the transaction under point. +Optional argument STYLE may be `pending' or `cleared', depending +on which type of status the caller wishes to indicate (default is +`cleared'). +This function is rather complicated because it must preserve both +the overall formatting of the ledger entry, as well as ensuring +that the most minimal display format is used. This could be +achieved more certainly by passing the entry to ledger for +formatting, but doing so causes inline math expressions to be +dropped." + (interactive) + (let ((bounds (ledger-current-entry-bounds)) + clear cleared) + ;; Uncompact the entry, to make it easier to toggle the + ;; transaction + (save-excursion + (goto-char (car bounds)) + (skip-chars-forward "0-9./= \t") + (setq cleared (and (member (char-after) '(?\* ?\!)) + (char-after))) + (when cleared + (let ((here (point))) + (skip-chars-forward "*! ") + (let ((width (- (point) here))) + (when (> width 0) + (delete-region here (point)) + (if (search-forward " " (line-end-position) t) + (insert (make-string width ? )))))) + (forward-line) + (while (looking-at "[ \t]") + (skip-chars-forward " \t") + (insert cleared " ") + (if (search-forward " " (line-end-position) t) + (delete-char 2)) + (forward-line)))) + ;; Toggle the individual transaction + (save-excursion + (goto-char (line-beginning-position)) + (when (looking-at "[ \t]") + (skip-chars-forward " \t") + (let ((here (point)) + (cleared (member (char-after) '(?\* ?\!)))) + (skip-chars-forward "*! ") + (let ((width (- (point) here))) + (when (> width 0) + (delete-region here (point)) + (save-excursion + (if (search-forward " " (line-end-position) t) + (insert (make-string width ? )))))) + (let (inserted) + (if cleared + (if (and style (eq style 'cleared)) + (progn + (insert "* ") + (setq inserted t))) + (if (and style (eq style 'pending)) + (progn + (insert "! ") + (setq inserted t)) + (progn + (insert "* ") + (setq inserted t)))) + (if (and inserted + (search-forward " " (line-end-position) t)) + (delete-char 2)) + (setq clear inserted))))) + ;; Clean up the entry so that it displays minimally + (save-excursion + (goto-char (car bounds)) + (forward-line) + (let ((first t) + (state ? ) + (hetero nil)) + (while (and (not hetero) (looking-at "[ \t]")) + (skip-chars-forward " \t") + (let ((cleared (if (member (char-after) '(?\* ?\!)) + (char-after) + ? ))) + (if first + (setq state cleared + first nil) + (if (/= state cleared) + (setq hetero t)))) + (forward-line)) + (when (and (not hetero) (/= state ? )) + (goto-char (car bounds)) + (forward-line) + (while (looking-at "[ \t]") + (skip-chars-forward " \t") + (let ((here (point))) + (skip-chars-forward "*! ") + (let ((width (- (point) here))) + (when (> width 0) + (delete-region here (point)) + (if (search-forward " " (line-end-position) t) + (insert (make-string width ? )))))) + (forward-line)) + (goto-char (car bounds)) + (skip-chars-forward "0-9./= \t") + (insert state " ") + (if (search-forward " " (line-end-position) t) + (delete-char 2))))) + clear)) + +(defun ledger-toggle-current (&optional style) + (interactive) + (if ledger-clear-whole-entries + (ledger-toggle-current-entry style) + (ledger-toggle-current-transaction style))) + +(defvar ledger-mode-abbrev-table) + +;;;###autoload +(define-derived-mode ledger-mode text-mode "Ledger" + "A mode for editing ledger data files." + (set (make-local-variable 'comment-start) ";") + (set (make-local-variable 'comment-end) "") + (set (make-local-variable 'indent-tabs-mode) nil) + + (if (boundp 'font-lock-defaults) + (set (make-local-variable 'font-lock-defaults) + '(ledger-font-lock-keywords nil t))) + + (set (make-local-variable 'pcomplete-parse-arguments-function) + 'ledger-parse-arguments) + (set (make-local-variable 'pcomplete-command-completion-function) + 'ledger-complete-at-point) + (set (make-local-variable 'pcomplete-termination-string) "") + + (let ((map (current-local-map))) + (define-key map [(control ?c) (control ?a)] 'ledger-add-entry) + (define-key map [(control ?c) (control ?d)] 'ledger-delete-current-entry) + (define-key map [(control ?c) (control ?y)] 'ledger-set-year) + (define-key map [(control ?c) (control ?m)] 'ledger-set-month) + (define-key map [(control ?c) (control ?c)] 'ledger-toggle-current) + (define-key map [(control ?c) (control ?r)] 'ledger-reconcile) + (define-key map [(control ?c) (control ?s)] 'ledger-sort) + (define-key map [tab] 'pcomplete) + (define-key map [(control ?i)] 'pcomplete) + (define-key map [(control ?c) tab] 'ledger-fully-complete-entry) + (define-key map [(control ?c) (control ?i)] 'ledger-fully-complete-entry) + (define-key map [(control ?c) (control ?o) (control ?r)] 'ledger-report) + (define-key map [(control ?c) (control ?o) (control ?g)] + 'ledger-report-goto) + (define-key map [(control ?c) (control ?o) (control ?a)] + 'ledger-report-redo) + (define-key map [(control ?c) (control ?o) (control ?s)] + 'ledger-report-save) + (define-key map [(control ?c) (control ?o) (control ?e)] + 'ledger-report-edit) + (define-key map [(control ?c) (control ?o) (control ?k)] + 'ledger-report-kill))) + +;; Reconcile mode + +(defvar ledger-buf nil) +(defvar ledger-acct nil) + +(defun ledger-display-balance () + (let ((buffer ledger-buf) + (account ledger-acct)) + (with-temp-buffer + (let ((exit-code (ledger-run-ledger buffer "-C" "balance" account))) + (if (/= 0 exit-code) + (message "Error determining cleared balance") + (goto-char (1- (point-max))) + (goto-char (line-beginning-position)) + (delete-horizontal-space) + (message "Cleared balance = %s" + (buffer-substring-no-properties (point) + (line-end-position)))))))) + +(defun ledger-reconcile-toggle () + (interactive) + (let ((where (get-text-property (point) 'where)) + (account ledger-acct) + (inhibit-read-only t) + cleared) + (when (equal (car where) "") + (with-current-buffer ledger-buf + (goto-char (cdr where)) + (setq cleared (ledger-toggle-current 'pending))) + (if cleared + (add-text-properties (line-beginning-position) + (line-end-position) + (list 'face 'bold)) + (remove-text-properties (line-beginning-position) + (line-end-position) + (list 'face)))) + (forward-line))) + +(defun ledger-auto-reconcile (balance date) + (interactive "sReconcile to balance (negative for a liability): \nsStatement date (default: now): ") + (let ((buffer ledger-buf) + (account ledger-acct) cleared) + ;; attempt to auto-reconcile in the background + (with-temp-buffer + (let ((exit-code + (ledger-run-ledger buffer "--format" "%xB\\n" + "--reconcile" balance "--reconcile-date" date + "register" account))) + (if (/= 0 exit-code) + (error "Failed to reconcile account '%s' to balance '%s'" + account balance) + (goto-char (point-min)) + (unless (looking-at "[0-9]") + (error (buffer-string))) + (while (not (eobp)) + (setq cleared + (cons (1+ (read (current-buffer))) cleared)) + (forward-line))))) + (goto-char (point-min)) + (with-current-buffer ledger-buf + (setq cleared (mapcar 'copy-marker (nreverse cleared)))) + (let ((inhibit-redisplay t)) + (dolist (pos cleared) + (while (and (not (eobp)) + (/= pos (cdr (get-text-property (point) 'where)))) + (forward-line)) + (unless (eobp) + (ledger-reconcile-toggle)))) + (goto-char (point-min)))) + +(defun ledger-reconcile-refresh () + (interactive) + (let ((inhibit-read-only t) + (line (count-lines (point-min) (point)))) + (erase-buffer) + (ledger-do-reconcile) + (set-buffer-modified-p t) + (goto-char (point-min)) + (forward-line line))) + +(defun ledger-reconcile-refresh-after-save () + (let ((buf (get-buffer "*Reconcile*"))) + (if buf + (with-current-buffer buf + (ledger-reconcile-refresh) + (set-buffer-modified-p nil))))) + +(defun ledger-reconcile-add () + (interactive) + (with-current-buffer ledger-buf + (call-interactively #'ledger-add-entry)) + (ledger-reconcile-refresh)) + +(defun ledger-reconcile-delete () + (interactive) + (let ((where (get-text-property (point) 'where))) + (when (equal (car where) "") + (with-current-buffer ledger-buf + (goto-char (cdr where)) + (ledger-delete-current-entry)) + (let ((inhibit-read-only t)) + (goto-char (line-beginning-position)) + (delete-region (point) (1+ (line-end-position))) + (set-buffer-modified-p t))))) + +(defun ledger-reconcile-visit () + (interactive) + (let ((where (get-text-property (point) 'where))) + (when (equal (car where) "") + (switch-to-buffer-other-window ledger-buf) + (goto-char (cdr where))))) + +(defun ledger-reconcile-save () + (interactive) + (with-current-buffer ledger-buf + (save-buffer)) + (set-buffer-modified-p nil) + (ledger-display-balance)) + +(defun ledger-reconcile-quit () + (interactive) + (kill-buffer (current-buffer))) + +(defun ledger-reconcile-finish () + (interactive) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (let ((where (get-text-property (point) 'where)) + (face (get-text-property (point) 'face))) + (if (and (eq face 'bold) + (equal (car where) "")) + (with-current-buffer ledger-buf + (goto-char (cdr where)) + (ledger-toggle-current 'cleared)))) + (forward-line 1))) + (ledger-reconcile-save)) + +(defun ledger-do-reconcile () + (let* ((buf ledger-buf) + (account ledger-acct) + (items + (with-temp-buffer + (let ((exit-code + (ledger-run-ledger buf "--uncleared" "emacs" account))) + (when (= 0 exit-code) + (goto-char (point-min)) + (unless (eobp) + (unless (looking-at "(") + (error (buffer-string))) + (read (current-buffer)))))))) + (dolist (item items) + (let ((index 1)) + (dolist (xact (nthcdr 5 item)) + (let ((beg (point)) + (where + (with-current-buffer buf + (cons + (nth 0 item) + (if ledger-clear-whole-entries + (copy-marker (nth 1 item)) + (copy-marker (nth 0 xact))))))) + (insert (format "%s %-30s %-25s %15s\n" + (format-time-string "%m/%d" (nth 2 item)) + (nth 4 item) (nth 1 xact) (nth 2 xact))) + (if (nth 3 xact) + (set-text-properties beg (1- (point)) + (list 'face 'bold + 'where where)) + (set-text-properties beg (1- (point)) + (list 'where where)))) + (setq index (1+ index))))) + (goto-char (point-min)) + (set-buffer-modified-p nil) + (toggle-read-only t))) + +(defun ledger-reconcile (account &optional arg) + (interactive "sAccount to reconcile: \nP") + (let ((buf (current-buffer)) + (rbuf (get-buffer "*Reconcile*"))) + (if rbuf + (kill-buffer rbuf)) + (add-hook 'after-save-hook 'ledger-reconcile-refresh-after-save) + (with-current-buffer + (pop-to-buffer (get-buffer-create "*Reconcile*")) + (ledger-reconcile-mode) + (set (make-local-variable 'ledger-buf) buf) + (set (make-local-variable 'ledger-acct) account) + (ledger-do-reconcile) + (when arg + (sit-for 0 0) + (call-interactively #'ledger-auto-reconcile))))) + +(defvar ledger-reconcile-mode-abbrev-table) + +(define-derived-mode ledger-reconcile-mode text-mode "Reconcile" + "A mode for reconciling ledger entries." + (let ((map (make-sparse-keymap))) + (define-key map [(control ?m)] 'ledger-reconcile-visit) + (define-key map [return] 'ledger-reconcile-visit) + (define-key map [(control ?c) (control ?c)] 'ledger-reconcile-finish) + (define-key map [(control ?c) (control ?r)] 'ledger-auto-reconcile) + (define-key map [(control ?x) (control ?s)] 'ledger-reconcile-save) + (define-key map [(control ?l)] 'ledger-reconcile-refresh) + (define-key map [? ] 'ledger-reconcile-toggle) + (define-key map [?a] 'ledger-reconcile-add) + (define-key map [?d] 'ledger-reconcile-delete) + (define-key map [?n] 'next-line) + (define-key map [?p] 'previous-line) + (define-key map [?r] 'ledger-auto-reconcile) + (define-key map [?s] 'ledger-reconcile-save) + (define-key map [?q] 'ledger-reconcile-quit) + (use-local-map map))) + +;; Context sensitivity + +(defconst ledger-line-config + '((entry + (("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*?\\)[ \t]*;\\(.*\\)[ \t]*$" + (date nil status nil nil code payee comment)) + ("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*\\)[ \t]*$" + (date nil status nil nil code payee)))) + (acct-transaction + (("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account commodity amount nil comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*$" + (indent account commodity amount nil)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account amount nil commodity comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*$" + (indent account amount nil commodity)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account amount nil commodity comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*$" + (indent account amount nil commodity)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]*$" + (indent account)))))) + +(defun ledger-extract-context-info (line-type pos) + "Get context info for current line. + +Assumes point is at beginning of line, and the pos argument specifies +where the \"users\" point was." + (let ((linfo (assoc line-type ledger-line-config)) + found field fields) + (dolist (re-info (nth 1 linfo)) + (let ((re (nth 0 re-info)) + (names (nth 1 re-info))) + (unless found + (when (looking-at re) + (setq found t) + (dotimes (i (length names)) + (when (nth i names) + (setq fields (append fields + (list + (list (nth i names) + (match-string-no-properties (1+ i)) + (match-beginning (1+ i)))))))) + (dolist (f fields) + (and (nth 1 f) + (>= pos (nth 2 f)) + (setq field (nth 0 f)))))))) + (list line-type field fields))) + +(defun ledger-context-at-point () + "Return a list describing the context around point. + +The contents of the list are the line type, the name of the field +point containing point, and for selected line types, the content of +the fields in the line in a association list." + (let ((pos (point))) + (save-excursion + (beginning-of-line) + (let ((first-char (char-after))) + (cond ((equal (point) (line-end-position)) + '(empty-line nil nil)) + ((memq first-char '(?\ ?\t)) + (ledger-extract-context-info 'acct-transaction pos)) + ((memq first-char '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) + (ledger-extract-context-info 'entry pos)) + ((equal first-char ?\=) + '(automated-entry nil nil)) + ((equal first-char ?\~) + '(period-entry nil nil)) + ((equal first-char ?\!) + '(command-directive)) + ((equal first-char ?\;) + '(comment nil nil)) + ((equal first-char ?Y) + '(default-year nil nil)) + ((equal first-char ?P) + '(commodity-price nil nil)) + ((equal first-char ?N) + '(price-ignored-commodity nil nil)) + ((equal first-char ?D) + '(default-commodity nil nil)) + ((equal first-char ?C) + '(commodity-conversion nil nil)) + ((equal first-char ?i) + '(timeclock-i nil nil)) + ((equal first-char ?o) + '(timeclock-o nil nil)) + ((equal first-char ?b) + '(timeclock-b nil nil)) + ((equal first-char ?h) + '(timeclock-h nil nil)) + (t + '(unknown nil nil))))))) + +(defun ledger-context-other-line (offset) + "Return a list describing context of line offset for existing position. + +Offset can be positive or negative. If run out of buffer before reaching +specified line, returns nil." + (save-excursion + (let ((left (forward-line offset))) + (if (not (equal left 0)) + nil + (ledger-context-at-point))))) + +(defun ledger-context-line-type (context-info) + (nth 0 context-info)) + +(defun ledger-context-current-field (context-info) + (nth 1 context-info)) + +(defun ledger-context-field-info (context-info field-name) + (assoc field-name (nth 2 context-info))) + +(defun ledger-context-field-present-p (context-info field-name) + (not (null (ledger-context-field-info context-info field-name)))) + +(defun ledger-context-field-value (context-info field-name) + (nth 1 (ledger-context-field-info context-info field-name))) + +(defun ledger-context-field-position (context-info field-name) + (nth 2 (ledger-context-field-info context-info field-name))) + +(defun ledger-context-field-end-position (context-info field-name) + (+ (ledger-context-field-position context-info field-name) + (length (ledger-context-field-value context-info field-name)))) + +(defun ledger-context-goto-field-start (context-info field-name) + (goto-char (ledger-context-field-position context-info field-name))) + +(defun ledger-context-goto-field-end (context-info field-name) + (goto-char (ledger-context-field-end-position context-info field-name))) + +(defun ledger-entry-payee () + "Returns the payee of the entry containing point or nil." + (let ((i 0)) + (while (eq (ledger-context-line-type (ledger-context-other-line i)) 'acct-transaction) + (setq i (- i 1))) + (let ((context-info (ledger-context-other-line i))) + (if (eq (ledger-context-line-type context-info) 'entry) + (ledger-context-field-value context-info 'payee) + nil)))) + +;; Ledger report mode + +(defvar ledger-report-buffer-name "*Ledger Report*") + +(defvar ledger-report-name nil) +(defvar ledger-report-cmd nil) +(defvar ledger-report-name-prompt-history nil) +(defvar ledger-report-cmd-prompt-history nil) +(defvar ledger-original-window-cfg nil) + +(defvar ledger-report-mode-abbrev-table) + +(define-derived-mode ledger-report-mode text-mode "Ledger-Report" + "A mode for viewing ledger reports." + (let ((map (make-sparse-keymap))) + (define-key map [? ] 'scroll-up) + (define-key map [?r] 'ledger-report-redo) + (define-key map [?s] 'ledger-report-save) + (define-key map [?k] 'ledger-report-kill) + (define-key map [?e] 'ledger-report-edit) + (define-key map [?q] 'ledger-report-quit) + (define-key map [(control ?c) (control ?l) (control ?r)] + 'ledger-report-redo) + (define-key map [(control ?c) (control ?l) (control ?S)] + 'ledger-report-save) + (define-key map [(control ?c) (control ?l) (control ?k)] + 'ledger-report-kill) + (define-key map [(control ?c) (control ?l) (control ?e)] + 'ledger-report-edit) + (use-local-map map))) + +(defun ledger-report-read-name () + "Read the name of a ledger report to use, with completion. + +The empty string and unknown names are allowed." + (completing-read "Report name: " + ledger-reports nil nil nil + 'ledger-report-name-prompt-history nil)) + +(defun ledger-report (report-name edit) + "Run a user-specified report from `ledger-reports'. + +Prompts the user for the name of the report to run. If no name is +entered, the user will be prompted for a command line to run. The +command line specified or associated with the selected report name +is run and the output is made available in another buffer for viewing. +If a prefix argument is given and the user selects a valid report +name, the user is prompted with the corresponding command line for +editing before the command is run. + +The output buffer will be in `ledger-report-mode', which defines +commands for saving a new named report based on the command line +used to generate the buffer, navigating the buffer, etc." + (interactive + (progn + (when (and (buffer-modified-p) + (y-or-n-p "Buffer modified, save it? ")) + (save-buffer)) + (let ((rname (ledger-report-read-name)) + (edit (not (null current-prefix-arg)))) + (list rname edit)))) + (let ((buf (current-buffer)) + (rbuf (get-buffer ledger-report-buffer-name)) + (wcfg (current-window-configuration))) + (if rbuf + (kill-buffer rbuf)) + (with-current-buffer + (pop-to-buffer (get-buffer-create ledger-report-buffer-name)) + (ledger-report-mode) + (set (make-local-variable 'ledger-buf) buf) + (set (make-local-variable 'ledger-report-name) report-name) + (set (make-local-variable 'ledger-original-window-cfg) wcfg) + (ledger-do-report (ledger-report-cmd report-name edit)) + (shrink-window-if-larger-than-buffer)))) + +(defun string-empty-p (s) + "Check for the empty string." + (string-equal "" s)) + +(defun ledger-report-name-exists (name) + "Check to see if the given report name exists. + +If name exists, returns the object naming the report, otherwise returns nil." + (unless (string-empty-p name) + (car (assoc name ledger-reports)))) + +(defun ledger-reports-add (name cmd) + "Add a new report to `ledger-reports'." + (setq ledger-reports (cons (list name cmd) ledger-reports))) + +(defun ledger-reports-custom-save () + "Save the `ledger-reports' variable using the customize framework." + (customize-save-variable 'ledger-reports ledger-reports)) + +(defun ledger-report-read-command (report-cmd) + "Read the command line to create a report." + (read-from-minibuffer "Report command line: " + (if (null report-cmd) "ledger " report-cmd) + nil nil 'ledger-report-cmd-prompt-history)) + +(defun ledger-report-ledger-file-format-specifier () + "Substitute the full path to master or current ledger file + +The master file name is determined by the ledger-master-file buffer-local +variable which can be set using file variables. If it is set, it is used, +otherwise the current buffer file is used." + (ledger-master-file)) + +(defun ledger-read-string-with-default (prompt default) + (let ((default-prompt (concat prompt + (if default + (concat " (" default "): ") + ": ")))) + (read-string default-prompt nil nil default))) + +(defun ledger-report-payee-format-specifier () + "Substitute a payee name + +The user is prompted to enter a payee and that is substitued. If +point is in an entry, the payee for that entry is used as the +default." + ;; It is intended copmletion should be available on existing + ;; payees, but the list of possible completions needs to be + ;; developed to allow this. + (ledger-read-string-with-default "Payee" (regexp-quote (ledger-entry-payee)))) + +(defun ledger-report-account-format-specifier () + "Substitute an account name + +The user is prompted to enter an account name, which can be any +regular expression identifying an account. If point is on an account +transaction line for an entry, the full account name on that line is +the default." + ;; It is intended completion should be available on existing account + ;; names, but it remains to be implemented. + (let* ((context (ledger-context-at-point)) + (default + (if (eq (ledger-context-line-type context) 'acct-transaction) + (regexp-quote (ledger-context-field-value context 'account)) + nil))) + (ledger-read-string-with-default "Account" default))) + +(defun ledger-report-expand-format-specifiers (report-cmd) + (let ((expanded-cmd report-cmd)) + (while (string-match "%(\\([^)]*\\))" expanded-cmd) + (let* ((specifier (match-string 1 expanded-cmd)) + (f (cdr (assoc specifier ledger-report-format-specifiers)))) + (if f + (setq expanded-cmd (replace-match + (save-match-data + (with-current-buffer ledger-buf + (shell-quote-argument (funcall f)))) + t t expanded-cmd)) + (progn + (set-window-configuration ledger-original-window-cfg) + (error "Invalid ledger report format specifier '%s'" specifier))))) + expanded-cmd)) + +(defun ledger-report-cmd (report-name edit) + "Get the command line to run the report." + (let ((report-cmd (car (cdr (assoc report-name ledger-reports))))) + ;; logic for substitution goes here + (when (or (null report-cmd) edit) + (setq report-cmd (ledger-report-read-command report-cmd))) + (setq report-cmd (ledger-report-expand-format-specifiers report-cmd)) + (set (make-local-variable 'ledger-report-cmd) report-cmd) + (or (string-empty-p report-name) + (ledger-report-name-exists report-name) + (ledger-reports-add report-name report-cmd) + (ledger-reports-custom-save)) + report-cmd)) + +(defun ledger-do-report (cmd) + "Run a report command line." + (goto-char (point-min)) + (insert (format "Report: %s\n" cmd) + (make-string (- (window-width) 1) ?=) + "\n") + (shell-command cmd t nil)) + +(defun ledger-report-goto () + "Goto the ledger report buffer." + (interactive) + (let ((rbuf (get-buffer ledger-report-buffer-name))) + (if (not rbuf) + (error "There is no ledger report buffer")) + (pop-to-buffer rbuf) + (shrink-window-if-larger-than-buffer))) + +(defun ledger-report-redo () + "Redo the report in the current ledger report buffer." + (interactive) + (ledger-report-goto) + (erase-buffer) + (ledger-do-report ledger-report-cmd)) + +(defun ledger-report-quit () + "Quit the ledger report buffer." + (interactive) + (ledger-report-goto) + (set-window-configuration ledger-original-window-cfg)) + +(defun ledger-report-kill () + "Kill the ledger report buffer." + (interactive) + (ledger-report-quit) + (kill-buffer (get-buffer ledger-report-buffer-name))) + +(defun ledger-report-edit () + "Edit the defined ledger reports." + (interactive) + (customize-variable 'ledger-reports)) + +(defun ledger-report-read-new-name () + "Read the name for a new report from the minibuffer." + (let ((name "")) + (while (string-empty-p name) + (setq name (read-from-minibuffer "Report name: " nil nil nil + 'ledger-report-name-prompt-history))) + name)) + +(defun ledger-report-save () + "Save the current report command line as a named report." + (interactive) + (ledger-report-goto) + (let (existing-name) + (when (string-empty-p ledger-report-name) + (setq ledger-report-name (ledger-report-read-new-name))) + + (while (setq existing-name (ledger-report-name-exists ledger-report-name)) + (cond ((y-or-n-p (format "Overwrite existing report named '%s' " + ledger-report-name)) + (when (string-equal + ledger-report-cmd + (car (cdr (assq existing-name ledger-reports)))) + (error "Current command is identical to existing saved one")) + (setq ledger-reports + (assq-delete-all existing-name ledger-reports))) + (t + (setq ledger-report-name (ledger-report-read-new-name))))) + + (ledger-reports-add ledger-report-name ledger-report-cmd) + (ledger-reports-custom-save))) + +;; In-place completion support + +(defun ledger-thing-at-point () + (let ((here (point))) + (goto-char (line-beginning-position)) + (cond ((looking-at "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.+?)\\)?\\s-+") + (goto-char (match-end 0)) + 'entry) + ((looking-at "^\\s-+\\([*!]\\s-+\\)?[[(]?\\(.\\)") + (goto-char (match-beginning 2)) + 'transaction) + (t + (ignore (goto-char here)))))) + +(defun ledger-parse-arguments () + "Parse whitespace separated arguments in the current region." + (let* ((info (save-excursion + (cons (ledger-thing-at-point) (point)))) + (begin (cdr info)) + (end (point)) + begins args) + (save-excursion + (goto-char begin) + (when (< (point) end) + (skip-chars-forward " \t\n") + (setq begins (cons (point) begins)) + (setq args (cons (buffer-substring-no-properties + (car begins) end) + args))) + (cons (reverse args) (reverse begins))))) + +(defun ledger-entries () + (let ((origin (point)) + entries-list) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) + (unless (and (>= origin (match-beginning 0)) + (< origin (match-end 0))) + (setq entries-list (cons (match-string-no-properties 3) + entries-list))))) + (pcomplete-uniqify-list (nreverse entries-list)))) + +(defvar ledger-account-tree nil) + +(defun ledger-find-accounts () + (let ((origin (point)) account-path elements) + (save-excursion + (setq ledger-account-tree (list t)) + (goto-char (point-min)) + (while (re-search-forward + "^[ \t]+\\([*!]\\s-+\\)?[[(]?\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)" nil t) + (unless (and (>= origin (match-beginning 0)) + (< origin (match-end 0))) + (setq account-path (match-string-no-properties 2)) + (setq elements (split-string account-path ":")) + (let ((root ledger-account-tree)) + (while elements + (let ((entry (assoc (car elements) root))) + (if entry + (setq root (cdr entry)) + (setq entry (cons (car elements) (list t))) + (nconc root (list entry)) + (setq root (cdr entry)))) + (setq elements (cdr elements))))))))) + +(defun ledger-accounts () + (ledger-find-accounts) + (let* ((current (caar (ledger-parse-arguments))) + (elements (and current (split-string current ":"))) + (root ledger-account-tree) + (prefix nil)) + (while (cdr elements) + (let ((entry (assoc (car elements) root))) + (if entry + (setq prefix (concat prefix (and prefix ":") + (car elements)) + root (cdr entry)) + (setq root nil elements nil))) + (setq elements (cdr elements))) + (and root + (sort + (mapcar (function + (lambda (x) + (let ((term (if prefix + (concat prefix ":" (car x)) + (car x)))) + (if (> (length (cdr x)) 1) + (concat term ":") + term)))) + (cdr root)) + 'string-lessp)))) + +(defun ledger-complete-at-point () + "Do appropriate completion for the thing at point" + (interactive) + (while (pcomplete-here + (if (eq (save-excursion + (ledger-thing-at-point)) 'entry) + (ledger-entries) + (ledger-accounts))))) + +(defun ledger-fully-complete-entry () + "Do appropriate completion for the thing at point" + (interactive) + (let ((name (caar (ledger-parse-arguments))) + xacts) + (save-excursion + (when (eq 'entry (ledger-thing-at-point)) + (when (re-search-backward + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + (regexp-quote name) "\\(\t\\|\n\\| [ \t]\\)") nil t) + (forward-line) + (while (looking-at "^\\s-+") + (setq xacts (cons (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)) + xacts)) + (forward-line)) + (setq xacts (nreverse xacts))))) + (when xacts + (save-excursion + (insert ?\n) + (while xacts + (insert (car xacts) ?\n) + (setq xacts (cdr xacts)))) + (forward-line) + (goto-char (line-end-position)) + (if (re-search-backward "\\(\t\\| [ \t]\\)" nil t) + (goto-char (match-end 0)))))) + +;; A sample function for $ users + +(defun ledger-align-dollars (&optional column) + (interactive "p") + (if (= column 1) + (setq column 48)) + (while (search-forward "$" nil t) + (backward-char) + (let ((col (current-column)) + (beg (point)) + target-col len) + (skip-chars-forward "-$0-9,.") + (setq len (- (point) beg)) + (setq target-col (- column len)) + (if (< col target-col) + (progn + (goto-char beg) + (insert (make-string (- target-col col) ? ))) + (move-to-column target-col) + (if (looking-back " ") + (delete-char (- col target-col)) + (skip-chars-forward "^ \t") + (delete-horizontal-space) + (insert " "))) + (forward-line)))) + +;; A sample entry sorting function, which works if entry dates are of +;; the form YYYY/mm/dd. + +(defun ledger-sort () + (interactive) + (save-excursion + (goto-char (point-min)) + (sort-subr + nil + (function + (lambda () + (if (re-search-forward + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) + (goto-char (match-beginning 0)) + (goto-char (point-max))))) + (function + (lambda () + (forward-paragraph)))))) + +;; General helper functions + +(defvar ledger-delete-after nil) + +(defun ledger-run-ledger (buffer &rest args) + "run ledger with supplied arguments" + (cond + ((null ledger-binary-path) + (error "The variable `ledger-binary-path' has not been set")) + ((not (file-exists-p ledger-binary-path)) + (error "The file `ledger-binary-path' (\"%s\") does not exist" + ledger-binary-path)) + ((not (file-executable-p ledger-binary-path)) + (error "The file `ledger-binary-path' (\"%s\") cannot be executed" + ledger-binary-path)) + (t + (let ((buf (current-buffer))) + (with-current-buffer buffer + (apply #'call-process-region + (append (list (point-min) (point-max) + ledger-binary-path ledger-delete-after + buf nil "-f" "-") + args))))))) + +(defun ledger-run-ledger-and-delete (buffer &rest args) + (let ((ledger-delete-after t)) + (apply #'ledger-run-ledger buffer args))) + +(defun ledger-set-year (newyear) + "Set ledger's idea of the current year to the prefix argument." + (interactive "p") + (if (= newyear 1) + (setq ledger-year (read-string "Year: " (ledger-current-year))) + (setq ledger-year (number-to-string newyear)))) + +(defun ledger-set-month (newmonth) + "Set ledger's idea of the current month to the prefix argument." + (interactive "p") + (if (= newmonth 1) + (setq ledger-month (read-string "Month: " (ledger-current-month))) + (setq ledger-month (format "%02d" newmonth)))) + +(defvar ledger-master-file nil) + +(defun ledger-master-file () + "Return the master file for a ledger file. + +The master file is either the file for the current ledger buffer or the +file specified by the buffer-local variable ledger-master-file. Typically +this variable would be set in a file local variable comment block at the +end of a ledger file which is included in some other file." + (if ledger-master-file + (expand-file-name ledger-master-file) + (buffer-file-name))) + +(provide 'ledger) + +;;; ledger.el ends here diff --git a/lisp/timeclock.el b/lisp/timeclock.el new file mode 100644 index 00000000..03159e94 --- /dev/null +++ b/lisp/timeclock.el @@ -0,0 +1,1362 @@ +;;; timeclock.el --- mode for keeping track of how much you work + +;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Free Software Foundation, Inc. + +;; Author: John Wiegley +;; Created: 25 Mar 1999 +;; Version: 2.6 +;; Keywords: calendar data + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; This mode is for keeping track of time intervals. You can use it +;; for whatever purpose you like, but the typical scenario is to keep +;; track of how much time you spend working on certain projects. +;; +;; Use `timeclock-in' when you start on a project, and `timeclock-out' +;; when you're done. Once you've collected some data, you can use +;; `timeclock-workday-remaining' to see how much time is left to be +;; worked today (where `timeclock-workday' specifies the length of the +;; working day), and `timeclock-when-to-leave' to calculate when you're free. + +;; You'll probably want to bind the timeclock commands to some handy +;; keystrokes. At the moment, C-x t is unused: +;; +;; (require 'timeclock) +;; +;; (define-key ctl-x-map "ti" 'timeclock-in) +;; (define-key ctl-x-map "to" 'timeclock-out) +;; (define-key ctl-x-map "tc" 'timeclock-change) +;; (define-key ctl-x-map "tr" 'timeclock-reread-log) +;; (define-key ctl-x-map "tu" 'timeclock-update-modeline) +;; (define-key ctl-x-map "tw" 'timeclock-when-to-leave-string) + +;; If you want Emacs to display the amount of time "left" to your +;; workday in the modeline, you can either set the value of +;; `timeclock-modeline-display' to t using M-x customize, or you +;; can add this code to your .emacs file: +;; +;; (require 'timeclock) +;; (timeclock-modeline-display) +;; +;; To cancel this modeline display at any time, just call +;; `timeclock-modeline-display' again. + +;; You may also want Emacs to ask you before exiting, if you are +;; currently working on a project. This can be done either by setting +;; `timeclock-ask-before-exiting' to t using M-x customize (this is +;; the default), or by adding the following to your .emacs file: +;; +;; (add-hook 'kill-emacs-query-functions 'timeclock-query-out) + +;; NOTE: If you change your .timelog file without using timeclock's +;; functions, or if you change the value of any of timeclock's +;; customizable variables, you should run the command +;; `timeclock-reread-log'. This will recompute any discrepancies in +;; your average working time, and will make sure that the various +;; display functions return the correct value. + +;;; History: + +;;; Code: + +(defgroup timeclock nil + "Keeping track time of the time that gets spent." + :group 'data) + +;;; User Variables: + +(defcustom timeclock-file (convert-standard-filename "~/.timelog") + "*The file used to store timeclock data in." + :type 'file + :group 'timeclock) + +(defcustom timeclock-workday (* 8 60 60) + "*The length of a work period." + :type 'integer + :group 'timeclock) + +(defcustom timeclock-relative t + "*Whether to maken reported time relative to `timeclock-workday'. +For example, if the length of a normal workday is eight hours, and you +work four hours on Monday, then the amount of time \"remaining\" on +Tuesday is twelve hours -- relative to an averaged work period of +eight hours -- or eight hours, non-relative. So relative time takes +into account any discrepancy of time under-worked or over-worked on +previous days. This only affects the timeclock modeline display." + :type 'boolean + :group 'timeclock) + +(defcustom timeclock-get-project-function 'timeclock-ask-for-project + "*The function used to determine the name of the current project. +When clocking in, and no project is specified, this function will be +called to determine what is the current project to be worked on. +If this variable is nil, no questions will be asked." + :type 'function + :group 'timeclock) + +(defcustom timeclock-get-reason-function 'timeclock-ask-for-reason + "*A function used to determine the reason for clocking out. +When clocking out, and no reason is specified, this function will be +called to determine what is the reason. +If this variable is nil, no questions will be asked." + :type 'function + :group 'timeclock) + +(defcustom timeclock-get-workday-function nil + "*A function used to determine the length of today's workday. +The first time that a user clocks in each day, this function will be +called to determine what is the length of the current workday. If +the return value is nil, or equal to `timeclock-workday', nothing special +will be done. If it is a quantity different from `timeclock-workday', +however, a record will be output to the timelog file to note the fact that +that day has a length that is different from the norm." + :type '(choice (const nil) function) + :group 'timeclock) + +(defcustom timeclock-ask-before-exiting t + "*If non-nil, ask if the user wants to clock out before exiting Emacs. +This variable only has effect if set with \\[customize]." + :set (lambda (symbol value) + (if value + (add-hook 'kill-emacs-query-functions 'timeclock-query-out) + (remove-hook 'kill-emacs-query-functions 'timeclock-query-out)) + (setq timeclock-ask-before-exiting value)) + :type 'boolean + :group 'timeclock) + +(defvar timeclock-update-timer nil + "The timer used to update `timeclock-mode-string'.") + +;; For byte-compiler. +(defvar display-time-hook) +(defvar timeclock-modeline-display) + +(defcustom timeclock-use-display-time t + "*If non-nil, use `display-time-hook' for doing modeline updates. +The advantage of this is that one less timer has to be set running +amok in Emacs' process space. The disadvantage is that it requires +you to have `display-time' running. If you don't want to use +`display-time', but still want the modeline to show how much time is +left, set this variable to nil. Changing the value of this variable +while timeclock information is being displayed in the modeline has no +effect. You should call the function `timeclock-modeline-display' with +a positive argument to force an update." + :set (lambda (symbol value) + (let ((currently-displaying + (and (boundp 'timeclock-modeline-display) + timeclock-modeline-display))) + ;; if we're changing to the state that + ;; `timeclock-modeline-display' is already using, don't + ;; bother toggling it. This happens on the initial loading + ;; of timeclock.el. + (if (and currently-displaying + (or (and value + (boundp 'display-time-hook) + (memq 'timeclock-update-modeline + display-time-hook)) + (and (not value) + timeclock-update-timer))) + (setq currently-displaying nil)) + (and currently-displaying + (set-variable 'timeclock-modeline-display nil)) + (setq timeclock-use-display-time value) + (and currently-displaying + (set-variable 'timeclock-modeline-display t)) + timeclock-use-display-time)) + :type 'boolean + :group 'timeclock + :require 'time) + +(defcustom timeclock-first-in-hook nil + "*A hook run for the first \"in\" event each day. +Note that this hook is run before recording any events. Thus the +value of `timeclock-hours-today', `timeclock-last-event' and the +return value of function `timeclock-last-period' are relative previous +to today." + :type 'hook + :group 'timeclock) + +(defcustom timeclock-load-hook nil + "*Hook that gets run after timeclock has been loaded." + :type 'hook + :group 'timeclock) + +(defcustom timeclock-in-hook nil + "*A hook run every time an \"in\" event is recorded." + :type 'hook + :group 'timeclock) + +(defcustom timeclock-day-over-hook nil + "*A hook that is run when the workday has been completed. +This hook is only run if the current time remaining is being displayed +in the modeline. See the variable `timeclock-modeline-display'." + :type 'hook + :group 'timeclock) + +(defcustom timeclock-out-hook nil + "*A hook run every time an \"out\" event is recorded." + :type 'hook + :group 'timeclock) + +(defcustom timeclock-done-hook nil + "*A hook run every time a project is marked as completed." + :type 'hook + :group 'timeclock) + +(defcustom timeclock-event-hook nil + "*A hook run every time any event is recorded." + :type 'hook + :group 'timeclock) + +(defvar timeclock-last-event nil + "A list containing the last event that was recorded. +The format of this list is (CODE TIME PROJECT).") + +(defvar timeclock-last-event-workday nil + "The number of seconds in the workday of `timeclock-last-event'.") + +;;; Internal Variables: + +(defvar timeclock-discrepancy nil + "A variable containing the time discrepancy before the last event. +Normally, timeclock assumes that you intend to work for +`timeclock-workday' seconds every day. Any days in which you work +more or less than this amount is considered either a positive or +a negative discrepancy. If you work in such a manner that the +discrepancy is always brought back to zero, then you will by +definition have worked an average amount equal to `timeclock-workday' +each day.") + +(defvar timeclock-elapsed nil + "A variable containing the time elapsed for complete periods today. +This value is not accurate enough to be useful by itself. Rather, +call `timeclock-workday-elapsed', to determine how much time has been +worked so far today. Also, if `timeclock-relative' is nil, this value +will be the same as `timeclock-discrepancy'.") ; ? gm + +(defvar timeclock-last-period nil + "Integer representing the number of seconds in the last period. +Note that you shouldn't access this value, but instead should use the +function `timeclock-last-period'.") + +(defvar timeclock-mode-string nil + "The timeclock string (optionally) displayed in the modeline. +The time is bracketed by <> if you are clocked in, otherwise by [].") + +(defvar timeclock-day-over nil + "The date of the last day when notified \"day over\" for.") + +;;; User Functions: + +;;;###autoload +(defun timeclock-modeline-display (&optional arg) + "Toggle display of the amount of time left today in the modeline. +If `timeclock-use-display-time' is non-nil (the default), then +the function `display-time-mode' must be active, and the modeline +will be updated whenever the time display is updated. Otherwise, +the timeclock will use its own sixty second timer to do its +updating. With prefix ARG, turn modeline display on if and only +if ARG is positive. Returns the new status of timeclock modeline +display (non-nil means on)." + (interactive "P") + ;; cf display-time-mode. + (setq timeclock-mode-string "") + (or global-mode-string (setq global-mode-string '(""))) + (let ((on-p (if arg + (> (prefix-numeric-value arg) 0) + (not timeclock-modeline-display)))) + (if on-p + (progn + (or (memq 'timeclock-mode-string global-mode-string) + (setq global-mode-string + (append global-mode-string '(timeclock-mode-string)))) + (unless (memq 'timeclock-update-modeline timeclock-event-hook) + (add-hook 'timeclock-event-hook 'timeclock-update-modeline)) + (when timeclock-update-timer + (cancel-timer timeclock-update-timer) + (setq timeclock-update-timer nil)) + (if (boundp 'display-time-hook) + (remove-hook 'display-time-hook 'timeclock-update-modeline)) + (if timeclock-use-display-time + (progn + ;; Update immediately so there is a visible change + ;; on calling this function. + (if display-time-mode (timeclock-update-modeline) + (message "Activate `display-time-mode' to see \ +timeclock information")) + (add-hook 'display-time-hook 'timeclock-update-modeline)) + (setq timeclock-update-timer + (run-at-time nil 60 'timeclock-update-modeline)))) + (setq global-mode-string + (delq 'timeclock-mode-string global-mode-string)) + (remove-hook 'timeclock-event-hook 'timeclock-update-modeline) + (if (boundp 'display-time-hook) + (remove-hook 'display-time-hook + 'timeclock-update-modeline)) + (when timeclock-update-timer + (cancel-timer timeclock-update-timer) + (setq timeclock-update-timer nil))) + (force-mode-line-update) + (setq timeclock-modeline-display on-p))) + +;; This has to be here so that the function definition of +;; `timeclock-modeline-display' is known to the "set" function. +(defcustom timeclock-modeline-display nil + "Toggle modeline display of time remaining. +You must modify via \\[customize] for this variable to have an effect." + :set (lambda (symbol value) + (setq timeclock-modeline-display + (timeclock-modeline-display (or value 0)))) + :type 'boolean + :group 'timeclock + :require 'timeclock) + +(defsubst timeclock-time-to-date (time) + "Convert the TIME value to a textual date string." + (format-time-string "%Y/%m/%d" time)) + +;;;###autoload +(defun timeclock-in (&optional arg project find-project) + "Clock in, recording the current time moment in the timelog. +With a numeric prefix ARG, record the fact that today has only that +many hours in it to be worked. If arg is a non-numeric prefix arg +\(non-nil, but not a number), 0 is assumed (working on a holiday or +weekend). *If not called interactively, ARG should be the number of +_seconds_ worked today*. This feature only has effect the first time +this function is called within a day. + +PROJECT is the project being clocked into. If PROJECT is nil, and +FIND-PROJECT is non-nil -- or the user calls `timeclock-in' +interactively -- call the function `timeclock-get-project-function' to +discover the name of the project." + (interactive + (list (and current-prefix-arg + (if (numberp current-prefix-arg) + (* current-prefix-arg 60 60) + 0)))) + (if (equal (car timeclock-last-event) "i") + (error "You've already clocked in!") + (unless timeclock-last-event + (timeclock-reread-log)) + ;; Either no log file, or day has rolled over. + (unless (and timeclock-last-event + (equal (timeclock-time-to-date + (cadr timeclock-last-event)) + (timeclock-time-to-date (current-time)))) + (let ((workday (or (and (numberp arg) arg) + (and arg 0) + (and timeclock-get-workday-function + (funcall timeclock-get-workday-function)) + timeclock-workday))) + (run-hooks 'timeclock-first-in-hook) + ;; settle the discrepancy for the new day + (setq timeclock-discrepancy + (- (or timeclock-discrepancy 0) workday)) + (if (not (= workday timeclock-workday)) + (timeclock-log "h" (and (numberp arg) + (number-to-string arg)))))) + (timeclock-log "i" (or project + (and timeclock-get-project-function + (or find-project (interactive-p)) + (funcall timeclock-get-project-function)))) + (run-hooks 'timeclock-in-hook))) + +;;;###autoload +(defun timeclock-out (&optional arg reason find-reason) + "Clock out, recording the current time moment in the timelog. +If a prefix ARG is given, the user has completed the project that was +begun during the last time segment. + +REASON is the user's reason for clocking out. If REASON is nil, and +FIND-REASON is non-nil -- or the user calls `timeclock-out' +interactively -- call the function `timeclock-get-reason-function' to +discover the reason." + (interactive "P") + (or timeclock-last-event + (error "You haven't clocked in!")) + (if (equal (downcase (car timeclock-last-event)) "o") + (error "You've already clocked out!") + (timeclock-log + (if arg "O" "o") + (or reason + (and timeclock-get-reason-function + (or find-reason (interactive-p)) + (funcall timeclock-get-reason-function)))) + (run-hooks 'timeclock-out-hook) + (if arg + (run-hooks 'timeclock-done-hook)))) + +;; Should today-only be removed in favour of timeclock-relative? - gm +(defsubst timeclock-workday-remaining (&optional today-only) + "Return the number of seconds until the workday is complete. +The amount returned is relative to the value of `timeclock-workday'. +If TODAY-ONLY is non-nil, the value returned will be relative only to +the time worked today, and not to past time." + (let ((discrep (timeclock-find-discrep))) + (if discrep + (- (if today-only (cadr discrep) + (car discrep))) + 0.0))) + +;;;###autoload +(defun timeclock-status-string (&optional show-seconds today-only) + "Report the overall timeclock status at the present moment. +If SHOW-SECONDS is non-nil, display second resolution. +If TODAY-ONLY is non-nil, the display will be relative only to time +worked today, ignoring the time worked on previous days." + (interactive "P") + (let ((remainder (timeclock-workday-remaining)) ; today-only? + (last-in (equal (car timeclock-last-event) "i")) + status) + (setq status + (format "Currently %s since %s (%s), %s %s, leave at %s" + (if last-in "IN" "OUT") + (if show-seconds + (format-time-string "%-I:%M:%S %p" + (nth 1 timeclock-last-event)) + (format-time-string "%-I:%M %p" + (nth 1 timeclock-last-event))) + (or (nth 2 timeclock-last-event) + (if last-in "**UNKNOWN**" "workday over")) + (timeclock-seconds-to-string remainder show-seconds t) + (if (> remainder 0) + "remaining" "over") + (timeclock-when-to-leave-string show-seconds today-only))) + (if (interactive-p) + (message status) + status))) + +;;;###autoload +(defun timeclock-change (&optional arg project) + "Change to working on a different project. +This clocks out of the current project, then clocks in on a new one. +With a prefix ARG, consider the previous project as finished at the +time of changeover. PROJECT is the name of the last project you were +working on." + (interactive "P") + (timeclock-out arg) + (timeclock-in nil project (interactive-p))) + +;;;###autoload +(defun timeclock-query-out () + "Ask the user whether to clock out. +This is a useful function for adding to `kill-emacs-query-functions'." + (and (equal (car timeclock-last-event) "i") + (y-or-n-p "You're currently clocking time, clock out? ") + (timeclock-out)) + ;; Unconditionally return t for `kill-emacs-query-functions'. + t) + +;;;###autoload +(defun timeclock-reread-log () + "Re-read the timeclock, to account for external changes. +Returns the new value of `timeclock-discrepancy'." + (interactive) + (setq timeclock-discrepancy nil) + (timeclock-find-discrep) + (if (and timeclock-discrepancy timeclock-modeline-display) + (timeclock-update-modeline)) + timeclock-discrepancy) + +(defun timeclock-seconds-to-string (seconds &optional show-seconds + reverse-leader) + "Convert SECONDS into a compact time string. +If SHOW-SECONDS is non-nil, make the resolution of the return string +include the second count. If REVERSE-LEADER is non-nil, it means to +output a \"+\" if the time value is negative, rather than a \"-\". +This is used when negative time values have an inverted meaning (such +as with time remaining, where negative time really means overtime)." + (if show-seconds + (format "%s%d:%02d:%02d" + (if (< seconds 0) (if reverse-leader "+" "-") "") + (truncate (/ (abs seconds) 60 60)) + (% (truncate (/ (abs seconds) 60)) 60) + (% (truncate (abs seconds)) 60)) + (format "%s%d:%02d" + (if (< seconds 0) (if reverse-leader "+" "-") "") + (truncate (/ (abs seconds) 60 60)) + (% (truncate (/ (abs seconds) 60)) 60)))) + +(defsubst timeclock-currently-in-p () + "Return non-nil if the user is currently clocked in." + (equal (car timeclock-last-event) "i")) + +;;;###autoload +(defun timeclock-workday-remaining-string (&optional show-seconds + today-only) + "Return a string representing the amount of time left today. +Display second resolution if SHOW-SECONDS is non-nil. If TODAY-ONLY +is non-nil, the display will be relative only to time worked today. +See `timeclock-relative' for more information about the meaning of +\"relative to today\"." + (interactive) + (let ((string (timeclock-seconds-to-string + (timeclock-workday-remaining today-only) + show-seconds t))) + (if (interactive-p) + (message string) + string))) + +(defsubst timeclock-workday-elapsed () + "Return the number of seconds worked so far today. +If RELATIVE is non-nil, the amount returned will be relative to past +time worked. The default is to return only the time that has elapsed +so far today." + (let ((discrep (timeclock-find-discrep))) + (if discrep + (nth 2 discrep) + 0.0))) + +;;;###autoload +(defun timeclock-workday-elapsed-string (&optional show-seconds) + "Return a string representing the amount of time worked today. +Display seconds resolution if SHOW-SECONDS is non-nil. If RELATIVE is +non-nil, the amount returned will be relative to past time worked." + (interactive) + (let ((string (timeclock-seconds-to-string (timeclock-workday-elapsed) + show-seconds))) + (if (interactive-p) + (message string) + string))) + +(defsubst timeclock-time-to-seconds (time) + "Convert TIME to a floating point number." + (+ (* (car time) 65536.0) + (cadr time) + (/ (or (car (cdr (cdr time))) 0) 1000000.0))) + +(defsubst timeclock-seconds-to-time (seconds) + "Convert SECONDS (a floating point number) to an Emacs time structure." + (list (floor seconds 65536) + (floor (mod seconds 65536)) + (floor (* (- seconds (ffloor seconds)) 1000000)))) + +;; Should today-only be removed in favour of timeclock-relative? - gm +(defsubst timeclock-when-to-leave (&optional today-only) + "Return a time value representing the end of today's workday. +If TODAY-ONLY is non-nil, the value returned will be relative only to +the time worked today, and not to past time." + (timeclock-seconds-to-time + (- (timeclock-time-to-seconds (current-time)) + (let ((discrep (timeclock-find-discrep))) + (if discrep + (if today-only + (cadr discrep) + (car discrep)) + 0.0))))) + +;;;###autoload +(defun timeclock-when-to-leave-string (&optional show-seconds + today-only) + "Return a string representing the end of today's workday. +This string is relative to the value of `timeclock-workday'. If +SHOW-SECONDS is non-nil, the value printed/returned will include +seconds. If TODAY-ONLY is non-nil, the value returned will be +relative only to the time worked today, and not to past time." + ;; Should today-only be removed in favour of timeclock-relative? - gm + (interactive) + (let* ((then (timeclock-when-to-leave today-only)) + (string + (if show-seconds + (format-time-string "%-I:%M:%S %p" then) + (format-time-string "%-I:%M %p" then)))) + (if (interactive-p) + (message string) + string))) + +;;; Internal Functions: + +(defvar timeclock-project-list nil) +(defvar timeclock-last-project nil) + +(defun timeclock-completing-read (prompt alist &optional default) + "A version of `completing-read' that works on both Emacs and XEmacs." + (if (featurep 'xemacs) + (let ((str (completing-read prompt alist))) + (if (or (null str) (= (length str) 0)) + default + str)) + (completing-read prompt alist nil nil nil nil default))) + +(defun timeclock-ask-for-project () + "Ask the user for the project they are clocking into." + (timeclock-completing-read + (format "Clock into which project (default \"%s\"): " + (or timeclock-last-project + (car timeclock-project-list))) + (mapcar 'list timeclock-project-list) + (or timeclock-last-project + (car timeclock-project-list)))) + +(defvar timeclock-reason-list nil) + +(defun timeclock-ask-for-reason () + "Ask the user for the reason they are clocking out." + (timeclock-completing-read "Reason for clocking out: " + (mapcar 'list timeclock-reason-list))) + +(defun timeclock-update-modeline () + "Update the `timeclock-mode-string' displayed in the modeline. +The value of `timeclock-relative' affects the display as described in +that variable's documentation." + (interactive) + (let ((remainder (timeclock-workday-remaining (not timeclock-relative))) + (last-in (equal (car timeclock-last-event) "i"))) + (when (and (< remainder 0) + (not (and timeclock-day-over + (equal timeclock-day-over + (timeclock-time-to-date + (current-time)))))) + (setq timeclock-day-over + (timeclock-time-to-date (current-time))) + (run-hooks 'timeclock-day-over-hook)) + (setq timeclock-mode-string + (propertize + (format " %c%s%c " + (if last-in ?< ?[) + (timeclock-seconds-to-string remainder nil t) + (if last-in ?> ?])) + 'help-echo "timeclock: time remaining")))) + +(put 'timeclock-mode-string 'risky-local-variable t) + +(defun timeclock-log (code &optional project) + "Log the event CODE to the timeclock log, at the time of call. +If PROJECT is a string, it represents the project which the event is +being logged for. Normally only \"in\" events specify a project." + (with-current-buffer (find-file-noselect timeclock-file) + (goto-char (point-max)) + (if (not (bolp)) + (insert "\n")) + (let ((now (current-time))) + (insert code " " + (format-time-string "%Y/%m/%d %H:%M:%S" now) + (or (and project + (stringp project) + (> (length project) 0) + (concat " " project)) + "") + "\n") + (if (equal (downcase code) "o") + (setq timeclock-last-period + (- (timeclock-time-to-seconds now) + (timeclock-time-to-seconds + (cadr timeclock-last-event))) + timeclock-discrepancy + (+ timeclock-discrepancy + timeclock-last-period))) + (setq timeclock-last-event (list code now project))) + (save-buffer) + (run-hooks 'timeclock-event-hook) + (kill-buffer (current-buffer)))) + +(defvar timeclock-moment-regexp + (concat "\\([bhioO]\\)\\s-+" + "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)\\s-+" + "\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)[ \t]*" "\\([^\n]*\\)")) + +(defsubst timeclock-read-moment () + "Read the moment under point from the timelog." + (if (looking-at timeclock-moment-regexp) + (let ((code (match-string 1)) + (year (string-to-number (match-string 2))) + (mon (string-to-number (match-string 3))) + (mday (string-to-number (match-string 4))) + (hour (string-to-number (match-string 5))) + (min (string-to-number (match-string 6))) + (sec (string-to-number (match-string 7))) + (project (match-string 8))) + (list code (encode-time sec min hour mday mon year) project)))) + +(defun timeclock-last-period (&optional moment) + "Return the value of the last event period. +If the last event was a clock-in, the period will be open ended, and +growing every second. Otherwise, it is a fixed amount which has been +recorded to disk. If MOMENT is non-nil, use that as the current time. +This is only provided for coherency when used by +`timeclock-discrepancy'." + (if (equal (car timeclock-last-event) "i") + (- (timeclock-time-to-seconds (or moment (current-time))) + (timeclock-time-to-seconds + (cadr timeclock-last-event))) + timeclock-last-period)) + +(defsubst timeclock-entry-length (entry) + (- (timeclock-time-to-seconds (cadr entry)) + (timeclock-time-to-seconds (car entry)))) + +(defsubst timeclock-entry-begin (entry) + (car entry)) + +(defsubst timeclock-entry-end (entry) + (cadr entry)) + +(defsubst timeclock-entry-project (entry) + (nth 2 entry)) + +(defsubst timeclock-entry-comment (entry) + (nth 3 entry)) + + +(defsubst timeclock-entry-list-length (entry-list) + (let ((length 0)) + (while entry-list + (setq length (+ length (timeclock-entry-length (car entry-list)))) + (setq entry-list (cdr entry-list))) + length)) + +(defsubst timeclock-entry-list-begin (entry-list) + (timeclock-entry-begin (car entry-list))) + +(defsubst timeclock-entry-list-end (entry-list) + (timeclock-entry-end (car (last entry-list)))) + +(defsubst timeclock-entry-list-span (entry-list) + (- (timeclock-time-to-seconds (timeclock-entry-list-end entry-list)) + (timeclock-time-to-seconds (timeclock-entry-list-begin entry-list)))) + +(defsubst timeclock-entry-list-break (entry-list) + (- (timeclock-entry-list-span entry-list) + (timeclock-entry-list-length entry-list))) + +(defsubst timeclock-entry-list-projects (entry-list) + (let (projects) + (while entry-list + (let ((project (timeclock-entry-project (car entry-list)))) + (if projects + (add-to-list 'projects project) + (setq projects (list project)))) + (setq entry-list (cdr entry-list))) + projects)) + + +(defsubst timeclock-day-required (day) + (or (car day) timeclock-workday)) + +(defsubst timeclock-day-length (day) + (timeclock-entry-list-length (cdr day))) + +(defsubst timeclock-day-debt (day) + (- (timeclock-day-required day) + (timeclock-day-length day))) + +(defsubst timeclock-day-begin (day) + (timeclock-entry-list-begin (cdr day))) + +(defsubst timeclock-day-end (day) + (timeclock-entry-list-end (cdr day))) + +(defsubst timeclock-day-span (day) + (timeclock-entry-list-span (cdr day))) + +(defsubst timeclock-day-break (day) + (timeclock-entry-list-break (cdr day))) + +(defsubst timeclock-day-projects (day) + (timeclock-entry-list-projects (cdr day))) + +(defmacro timeclock-day-list-template (func) + `(let ((length 0)) + (while day-list + (setq length (+ length (,(eval func) (car day-list)))) + (setq day-list (cdr day-list))) + length)) + +(defun timeclock-day-list-required (day-list) + (timeclock-day-list-template 'timeclock-day-required)) + +(defun timeclock-day-list-length (day-list) + (timeclock-day-list-template 'timeclock-day-length)) + +(defun timeclock-day-list-debt (day-list) + (timeclock-day-list-template 'timeclock-day-debt)) + +(defsubst timeclock-day-list-begin (day-list) + (timeclock-day-begin (car day-list))) + +(defsubst timeclock-day-list-end (day-list) + (timeclock-day-end (car (last day-list)))) + +(defun timeclock-day-list-span (day-list) + (timeclock-day-list-template 'timeclock-day-span)) + +(defun timeclock-day-list-break (day-list) + (timeclock-day-list-template 'timeclock-day-break)) + +(defun timeclock-day-list-projects (day-list) + (let (projects) + (while day-list + (let ((projs (timeclock-day-projects (car day-list)))) + (while projs + (if projects + (add-to-list 'projects (car projs)) + (setq projects (list (car projs)))) + (setq projs (cdr projs)))) + (setq day-list (cdr day-list))) + projects)) + + +(defsubst timeclock-current-debt (&optional log-data) + (nth 0 (or log-data (timeclock-log-data)))) + +(defsubst timeclock-day-alist (&optional log-data) + (nth 1 (or log-data (timeclock-log-data)))) + +(defun timeclock-day-list (&optional log-data) + (let ((alist (timeclock-day-alist log-data)) + day-list) + (while alist + (setq day-list (cons (cdar alist) day-list) + alist (cdr alist))) + day-list)) + +(defsubst timeclock-project-alist (&optional log-data) + (nth 2 (or log-data (timeclock-log-data)))) + + +(defun timeclock-log-data (&optional recent-only filename) + "Return the contents of the timelog file, in a useful format. +If the optional argument RECENT-ONLY is non-nil, only show the contents +from the last point where the time debt (see below) was set. +If the optional argument FILENAME is non-nil, it is used instead of +the file specified by `timeclock-file.' + +A timelog contains data in the form of a single entry per line. +Each entry has the form: + + CODE YYYY/MM/DD HH:MM:SS [COMMENT] + +CODE is one of: b, h, i, o or O. COMMENT is optional when the code is +i, o or O. The meanings of the codes are: + + b Set the current time balance, or \"time debt\". Useful when + archiving old log data, when a debt must be carried forward. + The COMMENT here is the number of seconds of debt. + + h Set the required working time for the given day. This must + be the first entry for that day. The COMMENT in this case is + the number of hours in this workday. Floating point amounts + are allowed. + + i Clock in. The COMMENT in this case should be the name of the + project worked on. + + o Clock out. COMMENT is unnecessary, but can be used to provide + a description of how the period went, for example. + + O Final clock out. Whatever project was being worked on, it is + now finished. Useful for creating summary reports. + +When this function is called, it will return a data structure with the +following format: + + (DEBT ENTRIES-BY-DAY ENTRIES-BY-PROJECT) + +DEBT is a floating point number representing the number of seconds +\"owed\" before any work was done. For a new file (one without a 'b' +entry), this is always zero. + +The two entries lists have similar formats. They are both alists, +where the CAR is the index, and the CDR is a list of time entries. +For ENTRIES-BY-DAY, the CAR is a textual date string, of the form +YYYY/MM/DD. For ENTRIES-BY-PROJECT, it is the name of the project +worked on, or t for the default project. + +The CDR for ENTRIES-BY-DAY is slightly different than for +ENTRIES-BY-PROJECT. It has the following form: + + (DAY-LENGTH TIME-ENTRIES...) + +For ENTRIES-BY-PROJECT, there is no DAY-LENGTH member. It is simply a +list of TIME-ENTRIES. Note that if DAY-LENGTH is nil, it means +whatever is the default should be used. + +A TIME-ENTRY is a recorded time interval. It has the following format +\(although generally one does not have to manipulate these entries +directly; see below): + + (BEGIN-TIME END-TIME PROJECT [COMMENT] [FINAL-P]) + +Anyway, suffice it to say there are a lot of structures. Typically +the user is expected to manipulate to the day(s) or project(s) that he +or she wants, at which point the following helper functions may be +used: + + timeclock-day-required + timeclock-day-length + timeclock-day-debt + timeclock-day-begin + timeclock-day-end + timeclock-day-span + timeclock-day-break + timeclock-day-projects + + timeclock-day-list-required + timeclock-day-list-length + timeclock-day-list-debt + timeclock-day-list-begin + timeclock-day-list-end + timeclock-day-list-span + timeclock-day-list-break + timeclock-day-list-projects + + timeclock-entry-length + timeclock-entry-begin + timeclock-entry-end + timeclock-entry-project + timeclock-entry-comment + + timeclock-entry-list-length + timeclock-entry-list-begin + timeclock-entry-list-end + timeclock-entry-list-span + timeclock-entry-list-break + timeclock-entry-list-projects + +A few comments should make the use of the above functions obvious: + + `required' is the amount of time that must be spent during a day, or + sequence of days, in order to have no debt. + + `length' is the actual amount of time that was spent. + + `debt' is the difference between required time and length. A + negative debt signifies overtime. + + `begin' is the earliest moment at which work began. + + `end' is the final moment work was done. + + `span' is the difference between begin and end. + + `break' is the difference between span and length. + + `project' is the project that was worked on, and `projects' is a + list of all the projects that were worked on during a given period. + + `comment', where it applies, could mean anything. + +There are a few more functions available, for locating day and entry +lists: + + timeclock-day-alist LOG-DATA + timeclock-project-alist LOG-DATA + timeclock-current-debt LOG-DATA + +See the documentation for the given function if more info is needed." + (let* ((log-data (list 0.0 nil nil)) + (now (current-time)) + (todays-date (timeclock-time-to-date now)) + last-date-limited last-date-seconds last-date + (line 0) last beg day entry event) + (with-temp-buffer + (insert-file-contents (or filename timeclock-file)) + (when recent-only + (goto-char (point-max)) + (unless (re-search-backward "^b\\s-+" nil t) + (goto-char (point-min)))) + (while (or (setq event (timeclock-read-moment)) + (and beg (not last) + (setq last t event (list "o" now)))) + (setq line (1+ line)) + (cond ((equal (car event) "b") + (setcar log-data (string-to-number (nth 2 event)))) + ((equal (car event) "h") + (setq last-date-limited (timeclock-time-to-date (cadr event)) + last-date-seconds (* (string-to-number (nth 2 event)) + 3600.0))) + ((equal (car event) "i") + (if beg + (error "Error in format of timelog file, line %d" line) + (setq beg t)) + (setq entry (list (cadr event) nil + (and (> (length (nth 2 event)) 0) + (nth 2 event)))) + (let ((date (timeclock-time-to-date (cadr event)))) + (if (and last-date + (not (equal date last-date))) + (progn + (setcar (cdr log-data) + (cons (cons last-date day) + (cadr log-data))) + (setq day (list (and last-date-limited + last-date-seconds)))) + (unless day + (setq day (list (and last-date-limited + last-date-seconds))))) + (setq last-date date + last-date-limited nil))) + ((equal (downcase (car event)) "o") + (if (not beg) + (error "Error in format of timelog file, line %d" line) + (setq beg nil)) + (setcar (cdr entry) (cadr event)) + (let ((desc (and (> (length (nth 2 event)) 0) + (nth 2 event)))) + (if desc + (nconc entry (list (nth 2 event)))) + (if (equal (car event) "O") + (nconc entry (if desc + (list t) + (list nil t)))) + (nconc day (list entry)) + (setq desc (nth 2 entry)) + (let ((proj (assoc desc (nth 2 log-data)))) + (if (null proj) + (setcar (cddr log-data) + (cons (cons desc (list entry)) + (car (cddr log-data)))) + (nconc (cdr proj) (list entry))))))) + (forward-line)) + (if day + (setcar (cdr log-data) + (cons (cons last-date day) + (cadr log-data)))) + log-data))) + +(defun timeclock-find-discrep () + "Calculate time discrepancies, in seconds. +The result is a three element list, containing the total time +discrepancy, today's discrepancy, and the time worked today." + ;; This is not implemented in terms of the functions above, because + ;; it's a bit wasteful to read all of that data in, just to throw + ;; away more than 90% of the information afterwards. + ;; + ;; If it were implemented using those functions, it would look + ;; something like this: + ;; (let ((days (timeclock-day-alist (timeclock-log-data))) + ;; (total 0.0)) + ;; (while days + ;; (setq total (+ total (- (timeclock-day-length (cdar days)) + ;; (timeclock-day-required (cdar days)))) + ;; days (cdr days))) + ;; total) + (let* ((now (current-time)) + (todays-date (timeclock-time-to-date now)) + (first t) (accum 0) (elapsed 0) + event beg last-date avg + last-date-limited last-date-seconds) + (unless timeclock-discrepancy + (when (file-readable-p timeclock-file) + (setq timeclock-project-list nil + timeclock-last-project nil + timeclock-reason-list nil + timeclock-elapsed 0) + (with-temp-buffer + (insert-file-contents timeclock-file) + (goto-char (point-max)) + (unless (re-search-backward "^b\\s-+" nil t) + (goto-char (point-min))) + (while (setq event (timeclock-read-moment)) + (cond ((equal (car event) "b") + (setq accum (string-to-number (nth 2 event)))) + ((equal (car event) "h") + (setq last-date-limited + (timeclock-time-to-date (cadr event)) + last-date-seconds + (* (string-to-number (nth 2 event)) 3600.0))) + ((equal (car event) "i") + (when (and (nth 2 event) + (> (length (nth 2 event)) 0)) + (add-to-list 'timeclock-project-list (nth 2 event)) + (setq timeclock-last-project (nth 2 event))) + (let ((date (timeclock-time-to-date (cadr event)))) + (if (if last-date + (not (equal date last-date)) + first) + (setq first nil + accum (- accum (if last-date-limited + last-date-seconds + timeclock-workday)))) + (setq last-date date + last-date-limited nil) + (if beg + (error "Error in format of timelog file!") + (setq beg (timeclock-time-to-seconds (cadr event)))))) + ((equal (downcase (car event)) "o") + (if (and (nth 2 event) + (> (length (nth 2 event)) 0)) + (add-to-list 'timeclock-reason-list (nth 2 event))) + (if (not beg) + (error "Error in format of timelog file!") + (setq timeclock-last-period + (- (timeclock-time-to-seconds (cadr event)) beg) + accum (+ timeclock-last-period accum) + beg nil)) + (if (equal last-date todays-date) + (setq timeclock-elapsed + (+ timeclock-last-period timeclock-elapsed))))) + (setq timeclock-last-event event + timeclock-last-event-workday + (if (equal (timeclock-time-to-date now) last-date-limited) + last-date-seconds + timeclock-workday)) + (forward-line)) + (setq timeclock-discrepancy accum)))) + (unless timeclock-last-event-workday + (setq timeclock-last-event-workday timeclock-workday)) + (setq accum (or timeclock-discrepancy 0) + elapsed (or timeclock-elapsed elapsed)) + (if timeclock-last-event + (if (equal (car timeclock-last-event) "i") + (let ((last-period (timeclock-last-period now))) + (setq accum (+ accum last-period) + elapsed (+ elapsed last-period))) + (if (not (equal (timeclock-time-to-date + (cadr timeclock-last-event)) + (timeclock-time-to-date now))) + (setq accum (- accum timeclock-last-event-workday))))) + (list accum (- elapsed timeclock-last-event-workday) + elapsed))) + +;;; A reporting function that uses timeclock-log-data + +(defun timeclock-day-base (&optional time) + "Given a time within a day, return 0:0:0 within that day. +If optional argument TIME is non-nil, use that instead of the current time." + (let ((decoded (decode-time (or time (current-time))))) + (setcar (nthcdr 0 decoded) 0) + (setcar (nthcdr 1 decoded) 0) + (setcar (nthcdr 2 decoded) 0) + (apply 'encode-time decoded))) + +(defun timeclock-geometric-mean (l) + "Compute the geometric mean of the values in the list L." + (let ((total 0) + (count 0)) + (while l + (setq total (+ total (car l)) + count (1+ count) + l (cdr l))) + (if (> count 0) + (/ total count) + 0))) + +(defun timeclock-generate-report (&optional html-p) + "Generate a summary report based on the current timelog file. +By default, the report is in plain text, but if the optional argument +HTML-P is non-nil, HTML markup is added." + (interactive) + (let ((log (timeclock-log-data)) + (today (timeclock-day-base))) + (if html-p (insert "

")) + (insert "Currently ") + (let ((project (nth 2 timeclock-last-event)) + (begin (nth 1 timeclock-last-event)) + done) + (if (timeclock-currently-in-p) + (insert "IN") + (if (or (null project) (= (length project) 0)) + (progn (insert "Done Working Today") + (setq done t)) + (insert "OUT"))) + (unless done + (insert " since " (format-time-string "%Y/%m/%d %-I:%M %p" begin)) + (if html-p + (insert "
\n") + (insert "\n*")) + (if (timeclock-currently-in-p) + (insert "Working on ")) + (if html-p + (insert project "
\n") + (insert project "*\n")) + (let ((proj-data (cdr (assoc project (timeclock-project-alist log)))) + (two-weeks-ago (timeclock-seconds-to-time + (- (timeclock-time-to-seconds today) + (* 2 7 24 60 60)))) + two-week-len today-len) + (while proj-data + (if (not (time-less-p + (timeclock-entry-begin (car proj-data)) today)) + (setq today-len (timeclock-entry-list-length proj-data) + proj-data nil) + (if (and (null two-week-len) + (not (time-less-p + (timeclock-entry-begin (car proj-data)) + two-weeks-ago))) + (setq two-week-len (timeclock-entry-list-length proj-data))) + (setq proj-data (cdr proj-data)))) + (if (null two-week-len) + (setq two-week-len today-len)) + (if html-p (insert "

")) + (if today-len + (insert "\nTime spent on this task today: " + (timeclock-seconds-to-string today-len) + ". In the last two weeks: " + (timeclock-seconds-to-string two-week-len)) + (if two-week-len + (insert "\nTime spent on this task in the last two weeks: " + (timeclock-seconds-to-string two-week-len)))) + (if html-p (insert "
")) + (insert "\n" + (timeclock-seconds-to-string (timeclock-workday-elapsed)) + " worked today, " + (timeclock-seconds-to-string (timeclock-workday-remaining)) + " remaining, done at " + (timeclock-when-to-leave-string) "\n"))) + (if html-p (insert "

")) + (insert "\nThere have been " + (number-to-string + (length (timeclock-day-alist log))) + " days of activity, starting " + (caar (last (timeclock-day-alist log)))) + (if html-p (insert "

")) + (when html-p + (insert "

+ +

+ + + + + + + +") + (let* ((day-list (timeclock-day-list)) + (thirty-days-ago (timeclock-seconds-to-time + (- (timeclock-time-to-seconds today) + (* 30 24 60 60)))) + (three-months-ago (timeclock-seconds-to-time + (- (timeclock-time-to-seconds today) + (* 90 24 60 60)))) + (six-months-ago (timeclock-seconds-to-time + (- (timeclock-time-to-seconds today) + (* 180 24 60 60)))) + (one-year-ago (timeclock-seconds-to-time + (- (timeclock-time-to-seconds today) + (* 365 24 60 60)))) + (time-in (vector (list t) (list t) (list t) (list t) (list t))) + (time-out (vector (list t) (list t) (list t) (list t) (list t))) + (breaks (vector (list t) (list t) (list t) (list t) (list t))) + (workday (vector (list t) (list t) (list t) (list t) (list t))) + (lengths (vector '(0 0) thirty-days-ago three-months-ago + six-months-ago one-year-ago))) + ;; collect statistics from complete timelog + (while day-list + (let ((i 0) (l 5)) + (while (< i l) + (unless (time-less-p + (timeclock-day-begin (car day-list)) + (aref lengths i)) + (let ((base (timeclock-time-to-seconds + (timeclock-day-base + (timeclock-day-begin (car day-list)))))) + (nconc (aref time-in i) + (list (- (timeclock-time-to-seconds + (timeclock-day-begin (car day-list))) + base))) + (let ((span (timeclock-day-span (car day-list))) + (len (timeclock-day-length (car day-list))) + (req (timeclock-day-required (car day-list)))) + ;; If the day's actual work length is less than + ;; 70% of its span, then likely the exit time + ;; and break amount are not worthwhile adding to + ;; the statistic + (when (and (> span 0) + (> (/ (float len) (float span)) 0.70)) + (nconc (aref time-out i) + (list (- (timeclock-time-to-seconds + (timeclock-day-end (car day-list))) + base))) + (nconc (aref breaks i) (list (- span len)))) + (if req + (setq len (+ len (- timeclock-workday req)))) + (nconc (aref workday i) (list len))))) + (setq i (1+ i)))) + (setq day-list (cdr day-list))) + ;; average statistics + (let ((i 0) (l 5)) + (while (< i l) + (aset time-in i (timeclock-geometric-mean + (cdr (aref time-in i)))) + (aset time-out i (timeclock-geometric-mean + (cdr (aref time-out i)))) + (aset breaks i (timeclock-geometric-mean + (cdr (aref breaks i)))) + (aset workday i (timeclock-geometric-mean + (cdr (aref workday i)))) + (setq i (1+ i)))) + ;; Output the HTML table + (insert "\n") + (insert "\n") + (let ((i 0) (l 5)) + (while (< i l) + (insert "\n") + (setq i (1+ i)))) + (insert "\n") + + (insert "\n") + (insert "\n") + (let ((i 0) (l 5)) + (while (< i l) + (insert "\n") + (setq i (1+ i)))) + (insert "\n") + + (insert "\n") + (insert "\n") + (let ((i 0) (l 5)) + (while (< i l) + (insert "\n") + (setq i (1+ i)))) + (insert "\n") + + (insert "\n") + (insert "\n") + (let ((i 0) (l 5)) + (while (< i l) + (insert "\n") + (setq i (1+ i)))) + (insert "\n")) + (insert " + + +
StatisticsEntire-30 days-3 mons-6 mons-1 year
Time in" + (timeclock-seconds-to-string (aref time-in i)) + "
Time out" + (timeclock-seconds-to-string (aref time-out i)) + "
Break" + (timeclock-seconds-to-string (aref breaks i)) + "
Workday" + (timeclock-seconds-to-string (aref workday i)) + "
+ These are approximate figures
+
"))))) + +;;; A helpful little function + +(defun timeclock-visit-timelog () + "Open the file named by `timeclock-file' in another window." + (interactive) + (find-file-other-window timeclock-file)) + +(provide 'timeclock) + +(run-hooks 'timeclock-load-hook) + +;; make sure we know the list of reasons, projects, and have computed +;; the last event and current discrepancy. +(if (file-readable-p timeclock-file) + (timeclock-reread-log)) + +;;; arch-tag: a0be3377-deb6-44ec-b9a2-a7be28436a40 +;;; timeclock.el ends here diff --git a/main.cc b/main.cc deleted file mode 100644 index e4afa67a..00000000 --- a/main.cc +++ /dev/null @@ -1,476 +0,0 @@ -#if defined(USE_BOOST_PYTHON) -#include -#else -#include -#endif -#include - -using namespace ledger; - -#if 0 -class print_addr : public repitem_t::select_callback_t { - virtual void operator()(repitem_t * item) { - std::cout << item << std::endl; - } -}; -#endif - -static int read_and_report(report_t * report, int argc, char * argv[], - char * envp[]) -{ - session_t& session(*report->session); - - // Handle the command-line arguments - - std::list args; - process_arguments(argc - 1, argv + 1, false, report, args); - - if (args.empty()) { -#if 0 - help(std::cerr); -#endif - 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.empty(); - - 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(envp), "LEDGER_", report); - TRACE_FINISH(environment, 1); - - const char * p = std::getenv("HOME"); - string home = p ? p : ""; - - if (session.init_file.empty()) - session.init_file = home + "/.ledgerrc"; - if (session.price_db.empty()) - session.price_db = home + "/.pricedb"; - - if (session.cache_file.empty()) - session.cache_file = home + "/.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); - INFO("Price database is " << session.price_db); - INFO("Binary cache is " << session.cache_file); - INFO("Journal file is " << session.data_file); - - if (! session.use_cache) - INFO("Binary cache mechanism will not be used"); - - // Read the command word and create a command object based on it - - string verb = *arg++; - - std::auto_ptr command; - - if (verb == "register" || verb == "reg" || verb == "r") { -#if 1 - command.reset(new register_command); -#else - command = new format_command - ("register", either_or(report->format_string, - report->session->register_format)); -#endif - } -#if 0 - else if (verb == "balance" || verb == "bal" || verb == "b") { - if (! report->raw_mode) { - report->transforms.push_back(new accounts_transform); - report->transforms.push_back(new clean_transform); - report->transforms.push_back(new compact_transform); - } - command = new format_command - ("balance", either_or(report->format_string, - report->session->balance_format)); - } - else if (verb == "print" || verb == "p") { - if (! report->raw_mode) - report->transforms.push_back(new optimize_transform); - command = new format_command - ("print", either_or(report->format_string, - report->session->print_format)); - } - else if (verb == "equity") { - if (! report->raw_mode) - report->transforms.push_back(new accounts_transform); - command = new format_command - ("equity", either_or(report->format_string, - report->session->equity_format)); - } - else if (verb == "entry") - command = new entry_command; - else if (verb == "dump") - command = new dump_command; - else if (verb == "output") - command = new output_command; - else if (verb == "prices") - command = new prices_command; - else if (verb == "pricesdb") - command = new pricesdb_command; - else if (verb == "csv") - command = new csv_command; - else if (verb == "emacs" || verb == "lisp") - command = new emacs_command; -#endif - else if (verb == "xml") - command.reset(new xml_command); - else if (verb == "expr") - ; - else if (verb == "xpath") - ; - else if (verb == "parse") { - xml::xpath_t expr(*arg); - - IF_INFO() { - std::cout << "Value expression tree:" << std::endl; - expr.dump(std::cout); - std::cout << std::endl; - std::cout << "Value expression parsed was:" << std::endl; - expr.write(std::cout); - std::cout << std::endl << std::endl; - std::cout << "Result of calculation: "; - } - - std::cout << expr.calc((xml::document_t *)NULL, report). - strip_annotations() << std::endl; - - return 0; - } - else { - char buf[128]; - std::strcpy(buf, "command_"); - std::strcat(buf, verb.c_str()); - - // jww (2007-04-19): This is an error, since command is an - // auto_ptr! - if (xml::xpath_t::op_t * def = report->lookup(buf)) - command.reset(def->functor_obj()); - - if (! command.get()) - throw_(exception, string("Unrecognized command '") + verb + "'"); - } - - // 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.read_data(report->account); - INFO_FINISH(journal); - - 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); - - // 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 (! report->pager.empty()) { - status = pipe(pfd); - if (status == -1) - throw_(exception, "Failed to create pipe"); - - status = fork(); - if (status < 0) { - throw_(exception, "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(report->pager.c_str(), '/'); - if (arg0) - arg0++; - else - arg0 = report->pager.c_str(); // No slashes in pager. - - execlp(report->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 expr commands? Do so now. - - if (verb == "expr") { - xml::xpath_t expr(*arg); - - IF_INFO() { - *out << "Value expression tree:" << std::endl; - expr.dump(*out); - *out << std::endl; - *out << "Value expression parsed was:" << std::endl; - expr.write(*out); - *out << std::endl << std::endl; - *out << "Result of calculation: "; - } - - *out << expr.calc((xml::document_t *)NULL, report). - strip_annotations() << std::endl; - - return 0; - } - else if (verb == "xpath") { - std::cout << "XPath parsed:" << std::endl; - xml::xpath_t xpath(*arg); - xpath.write(*out); - *out << std::endl; - -#if 0 - std::auto_ptr items(repitem_t::wrap(&session, report, true)); - print_addr cb; - items->select(path.get(), cb); -#endif - return 0; - } - - // Create the an argument scope containing the report command's - // arguments, and then invoke the command. - - std::auto_ptr locals - (new xml::xpath_t::scope_t(report, xml::xpath_t::scope_t::ARGUMENT)); - - locals->args = new value_t::sequence_t; - locals->args.push_back(out); - locals->args.push_back(journal->document); - - if (command->wants_args) { - for (strings_list::iterator i = args.begin(); - i != args.end(); - i++) - locals->args.push_back(*i); - } else { - string regexps[4]; - - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. - - int base = 0; - for (strings_list::iterator i = arg; i != args.end(); i++) - if ((*i)[0] == '-') { - if ((*i)[1] == '-') { - if (base == 0) - base += 2; - continue; - } - if (! regexps[base + 1].empty()) - regexps[base + 1] += "|"; - regexps[base + 1] += (*i).substr(1); - } else { - if (! regexps[base].empty()) - regexps[base] += "|"; - regexps[base] += *i; - } - -#if 0 - // jww (2006-09-21): Escape the \ in these strings! - - if (! regexps[3].empty()) - report->transforms.push_front - (new remove_transform - (string("//entry[payee =~ /(") + regexps[3] + ")/]")); - - if (! regexps[2].empty()) - report->transforms.push_front - (new select_transform - (string("//entry[payee =~ /(") + regexps[2] + ")/]")); - - if (! regexps[1].empty()) - report->transforms.push_front - (new remove_transform - (string("//xact[account =~ /(") + regexps[1] + ")/]")); - - if (! regexps[0].empty()) - report->transforms.push_front - (new select_transform - (string("//xact[account =~ /(") + regexps[0] + ")/]")); -#endif - } - - INFO_START(transforms, "Applied transforms"); - report->apply_transforms(journal->document); - INFO_FINISH(transforms); - - INFO_START(command, "Did user command '" << verb << "'"); - value_t temp; - (*command)(temp, locals.get()); - INFO_FINISH(command); - - // Write out the binary cache, if need be - - if (session.use_cache && session.cache_dirty && - ! session.cache_file.empty()) { - TRACE_START(binary_cache, 1, "Wrote binary journal file"); - - std::ofstream stream(session.cache_file.c_str()); -#if 0 - write_binary_journal(stream, journal); -#endif - - TRACE_FINISH(binary_cache, 1); - } - -#if defined(FREE_MEMORY) - // Cleanup memory -- if this is a beta or development build. - - if (! report->output_file.empty()) - delete out; -#endif - - // If the user specified a pager, wait for it to exit now - -#ifdef HAVE_UNIX_PIPES - if (report->output_file.empty() && ! report->pager.empty()) { - delete out; - close(pfd[1]); - - // Wait for child to finish - wait(&status); - if (status & 0xffff != 0) - throw_(exception, "Something went wrong in the pager"); - } -#endif - - 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 defined(VERIFY_ON) - if (std::strcmp(argv[i], "--verify") == 0) - ledger::verify_enabled = true; -#endif -#if defined(DEBUG_ON) - if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { - ledger::_log_level = LOG_DEBUG; - ledger::_log_category = argv[i + 1]; - i++; - } -#endif -#if defined(TRACING_ON) - if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { - ledger::_log_level = LOG_TRACE; - ledger::_trace_level = std::atoi(argv[i + 1]); - i++; - } -#endif - } - - try { - std::ios::sync_with_stdio(false); - - ledger::initialize(); - -#if ! defined(FULL_DEBUG) - ledger::do_cleanup = false; -#endif - INFO("Ledger starting"); - - std::auto_ptr session(new ledger::session_t); - -#if 0 - session->register_parser(new binary_parser_t); -#endif -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - session->register_parser(new xml_parser_t); - session->register_parser(new gnucash_parser_t); -#endif -#ifdef HAVE_LIBOFX - session->register_parser(new ofx_parser_t); -#endif - session->register_parser(new qif_parser_t); - session->register_parser(new textual_parser_t); - - std::auto_ptr report(new ledger::report_t(session.get())); - - status = read_and_report(report.get(), argc, argv, envp); - - if (! ledger::do_cleanup) { - report.release(); - session.release(); - } - } -#if 0 - catch (error * err) { - std::cout.flush(); - // Push a null here since there's no file context - if (err->context.empty() || - ! dynamic_cast(err->context.front())) - err->context.push_front(new error_context("")); - err->reveal_context(std::cerr, "Error"); - std::cerr << err->what() << std::endl; - delete err; - } - catch (fatal * err) { - std::cout.flush(); - // Push a null here since there's no file context - if (err->context.empty() || - ! dynamic_cast(err->context.front())) - err->context.push_front(new error_context("")); - err->reveal_context(std::cerr, "Fatal"); - std::cerr << err->what() << std::endl; - delete err; - } -#endif - catch (const std::exception& err) { - std::cout.flush(); - std::cerr << "Error: " << err.what() << std::endl; - } - catch (int _status) { - status = _status; - } - - if (ledger::do_cleanup) - ledger::shutdown(); - - return status; -} - -// main.cc ends here. diff --git a/mask.cc b/mask.cc deleted file mode 100644 index 02cac880..00000000 --- a/mask.cc +++ /dev/null @@ -1,24 +0,0 @@ -#include "mask.h" - -namespace ledger { - -mask_t::mask_t(const 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++; - } - - expr.assign(p); -} - -} // namespace ledger diff --git a/mask.h b/mask.h deleted file mode 100644 index 82634c19..00000000 --- a/mask.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef _MASK_H -#define _MASK_H - -#include "utils.h" - -#include - -namespace ledger { - -class mask_t -{ - public: - bool exclude; - boost::regex expr; - - explicit mask_t(const string& pattern); - mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {} - - bool match(const string& str) const { - return boost::regex_match(str, expr) && ! exclude; - } -}; - -} // namespace ledger - -#endif // _MASK_H diff --git a/ofx.cc b/ofx.cc deleted file mode 100644 index f0bce1f2..00000000 --- a/ofx.cc +++ /dev/null @@ -1,218 +0,0 @@ -#include "ofx.h" - -namespace ledger { - -typedef std::map accounts_map; -typedef std::pair accounts_pair; - -typedef std::map commodities_map; -typedef std::pair 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_("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 << " " << default_commodity->base_symbol(); - xact->cost = new amount_t(stream.str()); - } - - DEBUG_("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 , since it is not specified. - account = curr_journal->find_account(""); - entry->add_transaction(new transaction_t(account)); - - if (! curr_journal->add_entry(entry)) { - print_entry(std::cerr, *entry); -#if 0 - // jww (2005-02-09): uncomment - have_error = "The above entry does not balance"; -#endif - delete entry; - return -1; - } - return 0; -} - -int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data) -{ - 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 = 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_("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); - 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, "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 54713c17..00000000 --- a/ofx.h +++ /dev/null @@ -1,21 +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, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); -}; - -} // namespace ledger - -#endif // _OFX_H diff --git a/option.cc b/option.cc deleted file mode 100644 index f5b80016..00000000 --- a/option.cc +++ /dev/null @@ -1,224 +0,0 @@ -#include "option.h" -#if 0 -#ifdef USE_BOOST_PYTHON -#include "py_eval.h" -#endif -#endif - -#if 0 -#ifdef USE_BOOST_PYTHON -static ledger::option_t * find_option(const string& name); -#endif -#endif - -namespace ledger { - -namespace { - xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope, - const string& name) - { - char buf[128]; - std::strcpy(buf, "option_"); - char * p = &buf[7]; - for (const char * q = name.c_str(); *q; q++) { - if (*q == '-') - *p++ = '_'; - else - *p++ = *q; - } - *p = '\0'; - - return scope->lookup(buf); - } - - xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope, - const char letter) - { - char buf[9]; - std::strcpy(buf, "option_"); - buf[7] = letter; - buf[8] = '\0'; - - return scope->lookup(buf); - } - - void process_option(xml::xpath_t::functor_t * opt, xml::xpath_t::scope_t * scope, - const char * arg) - { -#if 0 - try { -#endif - std::auto_ptr args; - if (arg) { - args.reset(new xml::xpath_t::scope_t(scope, xml::xpath_t::scope_t::ARGUMENT)); - args->args.set_string(arg); - } - - value_t temp; - (*opt)(temp, args.get()); -#if 0 - } - catch (error * err) { - err->context.push_back - (new error_context - (string("While parsing option '--") + opt->long_opt + - "'" + (opt->short_opt != '\0' ? - (string(" (-") + opt->short_opt + "):") : ":"))); - throw err; - } -#endif - } -} - -bool process_option(const string& name, xml::xpath_t::scope_t * scope, - const char * arg) -{ - std::auto_ptr opt(find_option(scope, name)); - if (opt.get()) { - xml::xpath_t::functor_t * def = opt->functor_obj(); - if (def) { - process_option(def, scope, arg); - return true; - } - } - return false; -} - -void process_environment(const char ** envp, const string& tag, - xml::xpath_t::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 == '=') { -#if 0 - try { -#endif - if (! process_option(buf, scope, q + 1)) -#if 0 - throw new option_error("unknown option") -#endif - ; -#if 0 - } - catch (error * err) { - err->context.push_back - (new error_context - (string("While parsing environment variable option '") + - *p + "':")); - throw err; - } -#endif - } - } -} - -void process_arguments(int argc, char ** argv, const bool anywhere, - xml::xpath_t::scope_t * scope, - std::list& 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 - 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; - } - - std::auto_ptr opt(find_option(scope, name)); - if (! opt.get()) - throw_(option_exception, "illegal option --" << name); - - xml::xpath_t::functor_t * def = opt->functor_obj(); - if (! def) - throw_(option_exception, "illegal option --" << name); - - if (def->wants_args && value == NULL) { - value = *++i; - if (value == NULL) - throw_(option_exception, "missing option argument for --" << name); - } - process_option(def, scope, value); - } - else if ((*i)[1] == '\0') { - throw_(option_exception, "illegal option -"); - } - else { - std::list option_queue; - - int x = 1; - for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { - xml::xpath_t::op_t * opt = find_option(scope, c); - if (! opt) - throw_(option_exception, "illegal option -" << c); - - xml::xpath_t::functor_t * def = opt->functor_obj(); - if (! def) - throw_(option_exception, "illegal option -" << c); - - option_queue.push_back(opt); - } - - for (std::list::iterator - o = option_queue.begin(); - o != option_queue.end(); - o++) { - char * value = NULL; - - xml::xpath_t::functor_t * def = (*o)->functor_obj(); - assert(def); - - if (def->wants_args) { - value = *++i; - if (value == NULL) - throw_(option_exception, "missing option argument for -" << -#if 0 - def->short_opt -#else - '?' -#endif - ); - } - process_option(def, scope, value); - - delete *o; - } - } - - next: - ; - } -} - -} // namespace ledger diff --git a/option.h b/option.h deleted file mode 100644 index c9664ae3..00000000 --- a/option.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef _OPTION_H -#define _OPTION_H - -#include "xpath.h" - -namespace ledger { - -bool process_option(const string& name, xml::xpath_t::scope_t * scope, - const char * arg = NULL); - -void process_environment(const char ** envp, const string& tag, - xml::xpath_t::scope_t * scope); - -void process_arguments(int argc, char ** argv, const bool anywhere, - xml::xpath_t::scope_t * scope, - std::list& args); - -DECLARE_EXCEPTION(option_exception); - -} // namespace ledger - -#endif // _OPTION_H diff --git a/parser.h b/parser.h deleted file mode 100644 index 2bdc4622..00000000 --- a/parser.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef _PARSER_H -#define _PARSER_H - -#include "utils.h" - -namespace ledger { - -class account_t; -class journal_t; - -class parser_t -{ - public: - virtual ~parser_t() {} - - virtual bool test(std::istream& in) const = 0; - - virtual unsigned int parse(std::istream& in, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL) = 0; -}; - -DECLARE_EXCEPTION(parse_exception); - -/************************************************************************ - * - * General utility parsing functions - */ - -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'; \ -} - -#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 // _PARSER_H diff --git a/parsetime.yy b/parsetime.yy deleted file mode 100644 index 36bc08b9..00000000 --- a/parsetime.yy +++ /dev/null @@ -1,283 +0,0 @@ -%{ -#define YYSTYPE struct ledger::intorchar - -#include "times.h" -#include "FlexLexer.h" - -static struct std::tm * timeval; - -namespace { - ledger::moment_t moment; - - struct time_to_leave : std::exception {}; - - yyFlexLexer * lexer; - - inline void yyerror(const char *str) { - throw new ledger::datetime_error(str); - } - - inline int yylex(void) { - return lexer->yylex(); - } - - int month_to_int(const std::string& name) - { - switch (std::toupper(name[0])) { - case 'J': - if (std::tolower(name[1]) == 'a') - return 1; - else if (std::tolower(name[2]) == 'n') - return 6; - else - return 7; - case 'F': - return 2; - case 'M': - if (std::tolower(name[2]) == 'r') - return 3; - else - return 5; - case 'A': - if (std::tolower(name[1]) == 'p') - return 4; - else - return 8; - case 'S': - return 9; - case 'O': - return 10; - case 'N': - return 11; - case 'D': - return 12; - default: - std::cerr << "What?? (" << name << ")" << std::endl; - assert(0); - return -1; - } - } - - void set_mdy(const ledger::intorchar& month, - const ledger::intorchar& day, - const ledger::intorchar& year = ledger::intorchar(), - bool shortyear = false) - { - if (ledger::day_before_month) { - timeval->tm_mon = (day.ival == -1 ? - month_to_int(day.sval) : day.ival) - 1; - timeval->tm_mday = month.ival; - } else { - timeval->tm_mon = (month.ival == -1 ? - month_to_int(month.sval) : month.ival) - 1; - timeval->tm_mday = day.ival; - } - - if (year.ival != -1) - timeval->tm_year = (shortyear ? - (year.ival < 70 ? year.ival + 100 : year.ival) : - year.ival - 1900); - } - - void set_hms(const ledger::intorchar& ampm, - const ledger::intorchar& hour, - const ledger::intorchar& min = ledger::intorchar(), - const ledger::intorchar& sec = ledger::intorchar()) - { - if (! ampm.sval.empty() && - std::tolower(ampm.sval[0]) == 'a' && hour.ival == 12) - timeval->tm_hour = 0; - else if (! ampm.sval.empty() && - std::tolower(ampm.sval[0]) == 'p' && hour.ival == 12) - timeval->tm_hour = 12; - else if (hour.ival < 0 || (ampm.sval.empty() && hour.ival > 23) || - (! ampm.sval.empty() && hour.ival > 12)) - throw ledger::datetime_error("Hour out of range"); - else - timeval->tm_hour += hour.ival; - - if (min.ival < -1 || min.ival > 59) - throw ledger::datetime_error("Minute out of range"); - if (sec.ival < -1 || sec.ival > 59) - throw ledger::datetime_error("Seconds out of range"); - - timeval->tm_min = min.ival == -1 ? 0 : min.ival; - timeval->tm_sec = sec.ival == -1 ? 0 : sec.ival; - } -} - -%} - -%token TOK_FOURNUM -%token TOK_TWONUM -%token TOK_ONENUM -%token TOK_MONTH -%token TOK_AMPM - -%token TOK_SPACE - -%left '/' '-' '.' 'T' - -%% - -input: date -{ - throw time_to_leave(); -}; - -date: absdate opttime -{ - if (timeval->tm_gmtoff != -1) { - boost::posix_time::ptime::time_duration_type offset; - offset = boost::posix_time::seconds(timeval->tm_gmtoff); - moment = boost::posix_time::from_time_t(timegm(timeval)) - offset; - } else { - moment = boost::posix_time::ptime_from_tm(*timeval); - } -}; - -absdate: - isodate -| year '/' morday '/' morday { set_mdy($3, $5, $1); } -| year '-' morday '-' morday { set_mdy($3, $5, $1); } -| year '.' morday '.' morday { set_mdy($3, $5, $1); } -| morday '/' morday '/' year { set_mdy($1, $3, $5); } -| morday '-' morday '-' year { set_mdy($1, $3, $5); } -| morday '.' morday '.' year { set_mdy($1, $3, $5); } -| morday '.' morday { set_mdy($1, $3); } -| morday '/' morday { set_mdy($1, $3); } -| morday '-' morday { set_mdy($1, $3); } -| morday '/' morday '/' TOK_TWONUM { set_mdy($1, $3, $5, true); } -| morday '-' morday '-' TOK_TWONUM { set_mdy($1, $3, $5, true); } -| morday '.' morday '.' TOK_TWONUM { set_mdy($1, $3, $5, true); } -| year TOK_SPACE TOK_MONTH TOK_SPACE morday { set_mdy($3, $5, $1); } -| morday TOK_SPACE TOK_MONTH TOK_SPACE year { set_mdy($3, $1, $5); } -| TOK_MONTH TOK_SPACE morday { set_mdy($1, $3); } -| morday TOK_SPACE TOK_MONTH { set_mdy($3, $1); } -| year '-' TOK_MONTH '-' morday { set_mdy($3, $5, $1); } -| morday '-' TOK_MONTH '-' year { set_mdy($3, $1, $5); } -| TOK_MONTH '-' morday { set_mdy($1, $3); } -| morday '-' TOK_MONTH { set_mdy($3, $1); } -| TOK_MONTH TOK_SPACE morday ',' TOK_SPACE year { set_mdy($1, $3, $6); } -; - -opttime: /* epsilon */ | TOK_SPACE time ; - -time: - onetwo optspace TOK_AMPM { - if (std::tolower($3.sval[0]) == 'p') - timeval->tm_hour = 12; - else - timeval->tm_hour = 0; - - set_hms($3, $1); - } -| - onetwo ':' TOK_TWONUM optampm { - set_hms($4, $1, $3); - } -| - onetwo ':' TOK_TWONUM ':' TOK_TWONUM optampm { - set_hms($6, $1, $3, $5); - } -; - -onetwo: TOK_ONENUM { $$ = $1; } | TOK_TWONUM { $$ = $1; } ; - -optspace: /* epsilon */ | TOK_SPACE ; - -optampm: /* epsilon */ | - optspace TOK_AMPM { - if (std::tolower($2.sval[0]) == 'p') - timeval->tm_hour = 12; - else - timeval->tm_hour = 0; - $$ = $2; - }; - -isodate: - year TOK_FOURNUM optisotime -{ - timeval->tm_year = $1.ival - 1900; - timeval->tm_mon = $2.ival / 100 - 1; - timeval->tm_mday = $3.ival % 100; -}; - -optisotime: /* epsilon */ | - 'T' TOK_FOURNUM TOK_TWONUM optisozone -{ - timeval->tm_hour = $2.ival / 100; - timeval->tm_min = $2.ival % 100; - timeval->tm_sec = $3.ival; -}; - -optisozone: /* epsilon */ | - '-' TOK_FOURNUM { - timeval->tm_gmtoff = - (($2.ival / 100) * 3600 + ($2.ival % 100) * 60); - } -| '+' TOK_FOURNUM { - timeval->tm_gmtoff = (($2.ival / 100) * 3600 + ($2.ival % 100) * 60); - }; - -year: TOK_FOURNUM { $$ = $1; }; - -morday: - TOK_TWONUM { $$ = $1; } -| TOK_ONENUM { $$ = $1; }; - -%% - -int yywrap() -{ - return 1; -} - -ledger::moment_t parse_abs_datetime(std::istream& input) -{ - lexer = new yyFlexLexer(&input); - - struct std::tm temp; - std::memset(&temp, 0, sizeof(struct std::tm)); - temp.tm_year = 2002 - 1900; - temp.tm_gmtoff = -1; - - timeval = &temp; - - // jww (2007-04-19): Catch any boost errors thrown from here and - // push them onto the new error stack scheme. - try { - if (yyparse() == 0) { - delete lexer; - return moment; - } - } - catch (const time_to_leave&) { - delete lexer; - return moment; - } - catch (ledger::datetime_error *) { - delete lexer; - throw; - } - catch (...) { - delete lexer; - throw new ledger::datetime_error("Failed to parse date/time"); - } - delete lexer; - throw new ledger::datetime_error("Failed to parse date/time"); -} - -#ifdef MAIN - -namespace ledger { - bool day_before_month = false; -} - -int main() -{ - yydebug = 1; - std::cout << parse_abs_datetime(std::cin) << std::endl; - return 0; -} - -#endif // MAIN diff --git a/py_amount.cc b/py_amount.cc deleted file mode 100644 index 607d0be5..00000000 --- a/py_amount.cc +++ /dev/null @@ -1,242 +0,0 @@ -#include "py_eval.h" -#include "amount.h" - -using namespace boost::python; -using namespace ledger; - -int py_amount_quantity(amount_t& amount) -{ - std::ostringstream quant; - amount.print_quantity(quant); - return std::atol(quant.str().c_str()); -} - -void py_parse_1(amount_t& amount, const string& str, - unsigned char flags) { - amount.parse(str, flags); -} -void py_parse_2(amount_t& amount, const string& str) { - amount.parse(str); -} - -amount_t py_round_1(amount_t& amount, unsigned int prec) { - return amount.round(prec); -} -amount_t py_round_2(amount_t& amount) { - return amount.round(); -} - -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 moment_t& moment, - const moment_t& date, - const moment_t& last, - amount_t& price) { - call_method(self, "__call__", commodity, moment, date, last, price); - } -}; - -commodity_t * py_find_commodity(const string& symbol) -{ - return commodity_t::find(symbol); -} - -#define EXC_TRANSLATOR(type) \ - void exc_translate_ ## type(const type& err) { \ - PyErr_SetString(PyExc_ArithmeticError, err.what()); \ - } - -EXC_TRANSLATOR(amount_exception) - -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()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self += self) - .def(self += long()) - .def(self += double()) - - .def(self + self) - .def(self + long()) - .def(long() + self) - .def(self + double()) - .def(double() + self) - - .def(self -= self) - .def(self -= long()) - .def(self -= double()) - - .def(self - self) - .def(self - long()) - .def(long() - self) - .def(self - double()) - .def(double() - self) - - .def(self *= self) - .def(self *= long()) - .def(self *= double()) - - .def(self * self) - .def(self * long()) - .def(long() * self) - .def(self * double()) - .def(double() * self) - - .def(self /= self) - .def(self /= long()) - .def(self /= double()) - - .def(self / self) - .def(self / long()) - .def(long() / self) - .def(self / double()) - .def(double() / self) - - .def(- self) - - .def(self < self) - .def(self < long()) - .def(long() < self) - - .def(self <= self) - .def(self <= long()) - .def(long() <= self) - - .def(self > self) - .def(self > long()) - .def(long() > self) - - .def(self >= self) - .def(self >= long()) - .def(long() >= self) - - .def(self == self) - .def(self == long()) - .def(long() == self) - - .def(self != self) - .def(self != long()) - .def(long() != self) - - .def(! self) - - .def(self_ns::int_(self)) - .def(self_ns::float_(self)) - - .def("__str__", &amount_t::to_string) - .def("__repr__", &amount_t::to_fullstring) - - .def("has_commodity", &amount_t::has_commodity) - - .add_property("commodity", - make_function(&amount_t::commodity, - return_value_policy()), - make_function(&amount_t::set_commodity, - with_custodian_and_ward<1, 2>())) - - .def("annotate_commodity", &amount_t::annotate_commodity) - .def("strip_annotations", &amount_t::strip_annotations) - .def("clear_commodity", &amount_t::clear_commodity) - - //.add_static_property("full_strings", &amount_t::full_strings) - - .def("to_string", &amount_t::to_string) - .def("to_fullstring", &amount_t::to_fullstring) - .def("quantity_string", &amount_t::quantity_string) - - .def("exact", &amount_t::exact) - .staticmethod("exact") - - .def("__abs__", &amount_t::abs) - .def("compare", &amount_t::compare) - .def("date", &amount_t::date) - .def("negate", &amount_t::negate) - .def("null", &amount_t::null) - .def("parse", py_parse_1) - .def("parse", py_parse_2) - .def("price", &amount_t::price) - .def("realzero", &amount_t::realzero) - .def("reduce", &amount_t::reduce) - .def("round", py_round_1) - .def("round", py_round_2) - .def("sign", &amount_t::sign) - .def("unround", &amount_t::unround) - .def("value", &amount_t::value) - .def("zero", &amount_t::zero) - - .def("valid", &amount_t::valid) - - .def("initialize", &amount_t::initialize) - .staticmethod("initialize") - .def("shutdown", &amount_t::shutdown) - .staticmethod("shutdown") - ; - - 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") -#if 0 - .add_property("symbol", &commodity_t::symbol) - - .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) - //.add_property("updater", &commodity_t::updater) - - .add_property("smaller", - make_getter(&commodity_t::smaller, - return_value_policy()), - make_setter(&commodity_t::smaller, - return_value_policy())) - .add_property("larger", - make_getter(&commodity_t::larger, - return_value_policy()), - make_setter(&commodity_t::larger, - return_value_policy())) - - .def(self_ns::str(self)) - - .def("find", py_find_commodity, - return_value_policy()) - .staticmethod("find") - - .def("add_price", &commodity_t::add_price) - .def("remove_price", &commodity_t::remove_price) - .def("value", &commodity_t::value) - - .def("valid", &commodity_t::valid) -#endif - ; - -#define EXC_TRANSLATE(type) \ - register_exception_translator(&exc_translate_ ## type); - - EXC_TRANSLATE(amount_exception); -} diff --git a/py_balance.cc b/py_balance.cc deleted file mode 100644 index 372bf1e9..00000000 --- a/py_balance.cc +++ /dev/null @@ -1,202 +0,0 @@ -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()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self += self) - .def(self += other()) - .def(self += long()) - .def(self + self) - .def(self + other()) - .def(self + long()) - .def(self -= self) - .def(self -= other()) - .def(self -= long()) - .def(self - self) - .def(self - other()) - .def(self - long()) - .def(self *= self) - .def(self *= other()) - .def(self *= long()) - .def(self * self) - .def(self * other()) - .def(self * long()) - .def(self /= self) - .def(self /= other()) - .def(self /= long()) - .def(self / self) - .def(self / other()) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < other()) - .def(self < long()) - .def(self <= self) - .def(self <= other()) - .def(self <= long()) - .def(self > self) - .def(self > other()) - .def(self > long()) - .def(self >= self) - .def(self >= other()) - .def(self >= long()) - .def(self == self) - .def(self == other()) - .def(self == long()) - .def(self != self) - .def(self != other()) - .def(self != long()) - .def(! self) - - .def(self_ns::str(self)) - - .def("__abs__", &balance_t::abs) - .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()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - - .def(self += self) - .def(self += other()) - .def(self += other()) - .def(self += long()) - .def(self + self) - .def(self + other()) - .def(self + other()) - .def(self + long()) - .def(self -= self) - .def(self -= other()) - .def(self -= other()) - .def(self -= long()) - .def(self - self) - .def(self - other()) - .def(self - other()) - .def(self - long()) - .def(self *= self) - .def(self *= other()) - .def(self *= other()) - .def(self *= long()) - .def(self * self) - .def(self * other()) - .def(self * other()) - .def(self * long()) - .def(self /= self) - .def(self /= other()) - .def(self /= other()) - .def(self /= long()) - .def(self / self) - .def(self / other()) - .def(self / other()) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < other()) - .def(self < other()) - .def(self < long()) - .def(self <= self) - .def(self <= other()) - .def(self <= other()) - .def(self <= long()) - .def(self > self) - .def(self > other()) - .def(self > other()) - .def(self > long()) - .def(self >= self) - .def(self >= other()) - .def(self >= other()) - .def(self >= long()) - .def(self == self) - .def(self == other()) - .def(self == other()) - .def(self == long()) - .def(self != self) - .def(self != other()) - .def(self != other()) - .def(self != long()) - .def(! self) - - .def(self_ns::str(self)) - - .def("__abs__", &balance_pair_t::abs) - .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())) - ; -} diff --git a/py_eval.cc b/py_eval.cc deleted file mode 100644 index d66f6188..00000000 --- a/py_eval.cc +++ /dev/null @@ -1,202 +0,0 @@ -#include "py_eval.h" - -void export_amount(); -#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(); - -void shutdown_option(); -#endif - -namespace ledger { - -void initialize_for_python() -{ - export_amount(); -#if 0 - export_balance(); - export_value(); - - export_journal(); - export_parser(); - export_option(); - export_walk(); - export_format(); - export_report(); - export_valexpr(); -#endif -} - -void shutdown_for_python() -{ -#if 0 - shutdown_option(); -#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(xml::xpath_t::scope_t * parent) - : xml::xpath_t::scope_t(parent), - mmodule(borrowed(PyImport_AddModule("__main__"))), - nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) -{ - Py_Initialize(); - 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_(exception, "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_(exception, "Importing Python module " << str); - } -} - -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_(exception, "Evaluating Python code"); - } -} - -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_(exception, "Evaluating Python code"); - } -} - -void python_interpreter_t::functor_t::operator()(value_t& result, - xml::xpath_t::scope_t * locals) -{ - try { - if (! PyCallable_Check(func.ptr())) { - result = static_cast(extract(func.ptr())); - } else { - assert(locals->args.type == value_t::SEQUENCE); - if (locals->args.to_sequence()->size() > 0) { - list arglist; - for (value_t::sequence_t::iterator - i = locals->args.to_sequence()->begin(); - i != locals->args.to_sequence()->end(); - i++) - arglist.append(*i); - - if (PyObject * val = - PyObject_CallObject(func.ptr(), tuple(arglist).ptr())) { - result = extract(val)(); - Py_DECREF(val); - } - else if (PyObject * err = PyErr_Occurred()) { - PyErr_Print(); - throw_(xml::xpath_t::calc_exception, - "While calling Python function '" << name() << "'"); - } else { - assert(0); - } - } else { - result = call(func.ptr()); - } - } - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(xml::xpath_t::calc_exception, - "While calling Python function '" << name() << "'"); - } -} - -void python_interpreter_t::lambda_t::operator()(value_t& result, - xml::xpath_t::scope_t * locals) -{ - try { - assert(locals->args.type == value_t::SEQUENCE); - assert(locals->args.to_sequence()->size() == 1); - value_t item = locals->args[0]; - assert(item.type == value_t::POINTER); - result = call(func.ptr(), (xml::node_t *)*(void **)item.data); - } - catch (const error_already_set&) { - PyErr_Print(); - throw_(xml::xpath_t::calc_exception, - "While evaluating Python lambda expression"); - } -} - -} // namespace ledger diff --git a/py_eval.h b/py_eval.h deleted file mode 100644 index 4712fe23..00000000 --- a/py_eval.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef _PY_EVAL_H -#define _PY_EVAL_H - -#include "xpath.h" - -#include -#include -#include -#include - -#include - -#include "pyfstream.h" - -using namespace boost::python; - -namespace ledger { - -void initialize_for_python(); -void shutdown_for_python(); - -class python_interpreter_t : public xml::xpath_t::scope_t -{ - handle<> mmodule; - - public: - dict nspace; - - python_interpreter_t(xml::xpath_t::scope_t * parent); - - virtual ~python_interpreter_t() { - Py_Finalize(); - } - - object import(const string& name); - - enum py_eval_mode_t { - PY_EVAL_EXPR, - PY_EVAL_STMT, - PY_EVAL_MULTI - }; - - object eval(std::istream& in, py_eval_mode_t mode = PY_EVAL_EXPR); - object eval(const string& str, py_eval_mode_t mode = PY_EVAL_EXPR); - 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 : public xml::xpath_t::functor_t { - protected: - object func; - public: - functor_t(const string& name, object _func) - : xml::xpath_t::functor_t(name), func(_func) {} - - virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); - }; - - virtual void define(const string& name, xml::xpath_t::op_t * def) { - // Pass any definitions up to our parent - parent->define(name, def); - } - - virtual xml::xpath_t::op_t * lookup(const string& name) { - object func = eval(name); - if (! func) - return parent ? parent->lookup(name) : NULL; - return xml::xpath_t::wrap_functor(new functor_t(name, func)); - } - - class lambda_t : public functor_t { - public: - lambda_t(object code) : functor_t("", code) {} - - virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); - }; -}; - -} // namespace ledger - -#endif // _PY_EVAL_H diff --git a/py_format.cc b/py_format.cc deleted file mode 100644 index e2faf5dc..00000000 --- a/py_format.cc +++ /dev/null @@ -1,11 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -void export_format() -{ - class_< format_t > ("Format") - .def(init()) - .def("parse", &format_t::parse) - .def("format", &format_t::format) - ; -} diff --git a/py_journal.cc b/py_journal.cc deleted file mode 100644 index d309cf95..00000000 --- a/py_journal.cc +++ /dev/null @@ -1,374 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -entry_t& transaction_entry(const transaction_t& xact) -{ - return *xact.entry; -} - -unsigned int transactions_len(entry_base_t& entry) -{ - return entry.transactions.size(); -} - -transaction_t& transactions_getitem(entry_base_t& entry, int i) -{ - static int last_index = 0; - static entry_base_t * last_entry = NULL; - static transactions_list::iterator elem; - - std::size_t len = entry.transactions.size(); - - if (abs(i) >= len) { - PyErr_SetString(PyExc_IndexError, "Index out of range"); - throw_error_already_set(); - } - - if (&entry == last_entry && i == last_index + 1) { - last_index = i; - return **++elem; - } - - int x = i < 0 ? len + i : i; - elem = entry.transactions.begin(); - while (--x >= 0) - elem++; - - last_entry = &entry; - last_index = i; - - return **elem; -} - -unsigned int entries_len(journal_t& journal) -{ - return journal.entries.size(); -} - -entry_t& entries_getitem(journal_t& journal, int i) -{ - static int last_index = 0; - static journal_t * last_journal = NULL; - static entries_list::iterator elem; - - std::size_t len = journal.entries.size(); - - if (abs(i) >= len) { - PyErr_SetString(PyExc_IndexError, "Index out of range"); - throw_error_already_set(); - } - - if (&journal == last_journal && i == last_index + 1) { - last_index = i; - return **++elem; - } - - int x = i < 0 ? len + i : i; - elem = journal.entries.begin(); - while (--x >= 0) - elem++; - - last_journal = &journal; - last_index = i; - - return **elem; -} - -unsigned int accounts_len(account_t& account) -{ - return account.accounts.size(); -} - -account_t& accounts_getitem(account_t& account, int i) -{ - static int last_index = 0; - static account_t * last_account = NULL; - static accounts_map::iterator elem; - - std::size_t len = account.accounts.size(); - - if (abs(i) >= len) { - PyErr_SetString(PyExc_IndexError, "Index out of range"); - throw_error_already_set(); - } - - if (&account == last_account && i == last_index + 1) { - last_index = i; - return *(*++elem).second; - } - - int x = i < 0 ? len + i : i; - elem = account.accounts.begin(); - while (--x >= 0) - elem++; - - last_account = &account; - last_index = i; - - return *(*elem).second; -} - -PyObject * py_account_get_data(account_t& account) -{ - return (PyObject *) account.data; -} - -void py_account_set_data(account_t& account, PyObject * obj) -{ - account.data = obj; -} - -account_t * py_find_account_1(journal_t& journal, const string& name) -{ - return journal.find_account(name); -} - -account_t * py_find_account_2(journal_t& journal, const string& name, - const bool auto_create) -{ - return journal.find_account(name, auto_create); -} - -bool py_add_entry(journal_t& journal, entry_t * entry) { - return journal.add_entry(new entry_t(*entry)); -} - -void py_add_transaction(entry_base_t& entry, transaction_t * xact) { - return entry.add_transaction(new transaction_t(*xact)); -} - -struct entry_base_wrap : public entry_base_t -{ - PyObject * self; - entry_base_wrap(PyObject * self_) : self(self_) {} - - virtual bool valid() const { - return call_method(self, "valid"); - } -}; - -struct py_entry_finalizer_t : public entry_finalizer_t { - object pyobj; - py_entry_finalizer_t() {} - py_entry_finalizer_t(object obj) : pyobj(obj) {} - py_entry_finalizer_t(const py_entry_finalizer_t& other) - : pyobj(other.pyobj) {} - virtual bool operator()(entry_t& entry, bool post) { - return call(pyobj.ptr(), entry, post); - } -}; - -std::list py_finalizers; - -void py_add_entry_finalizer(journal_t& journal, object x) -{ - py_finalizers.push_back(py_entry_finalizer_t(x)); - journal.add_entry_finalizer(&py_finalizers.back()); -} - -void py_remove_entry_finalizer(journal_t& journal, object x) -{ - for (std::list::iterator i = py_finalizers.begin(); - i != py_finalizers.end(); - i++) - if ((*i).pyobj == x) { - journal.remove_entry_finalizer(&(*i)); - py_finalizers.erase(i); - return; - } -} - -void py_run_entry_finalizers(journal_t& journal, entry_t& entry, bool post) -{ - run_hooks(journal.entry_finalize_hooks, entry, post); -} - -#define EXC_TRANSLATOR(type) \ - void exc_translate_ ## type(const type& err) { \ - PyErr_SetString(PyExc_RuntimeError, err.what()); \ - } - -EXC_TRANSLATOR(balance_error) -EXC_TRANSLATOR(interval_expr_error) -EXC_TRANSLATOR(format_error) -EXC_TRANSLATOR(parse_error) - -value_t py_transaction_amount(transaction_t * xact) { - return value_t(xact->amount); -} - -transaction_t::state_t py_entry_state(entry_t * entry) { - transaction_t::state_t state; - if (entry->get_state(&state)) - return state; - else - return transaction_t::UNCLEARED; -} - -void export_journal() -{ - scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; - scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; - scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; - scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; - scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; - scope().attr("TRANSACTION_CALCULATED") = TRANSACTION_CALCULATED; - - enum_< transaction_t::state_t > ("State") - .value("Uncleared", transaction_t::UNCLEARED) - .value("Cleared", transaction_t::CLEARED) - .value("Pending", transaction_t::PENDING) - ; - - class_< transaction_t > ("Transaction") - .def(init >()) - .def(init >()) - - .def(self == self) - .def(self != self) - - .add_property("entry", - make_getter(&transaction_t::entry, - return_value_policy())) - .add_property("account", - make_getter(&transaction_t::account, - return_value_policy())) - - .add_property("amount", &py_transaction_amount) - .def_readonly("amount_expr", &transaction_t::amount_expr) - .add_property("cost", - make_getter(&transaction_t::cost, - return_internal_reference<1>())) - .def_readonly("cost_expr", &transaction_t::cost_expr) - - .def_readwrite("state", &transaction_t::state) - .def_readwrite("flags", &transaction_t::flags) - .def_readwrite("note", &transaction_t::note) - - .def_readonly("beg_pos", &transaction_t::beg_pos) - .def_readonly("beg_line", &transaction_t::beg_line) - .def_readonly("end_pos", &transaction_t::end_pos) - .def_readonly("end_line", &transaction_t::end_line) - - .def("actual_date", &transaction_t::actual_date) - .def("effective_date", &transaction_t::effective_date) - .def("date", &transaction_t::date) - - .def("use_effective_date", &transaction_t::use_effective_date) - - .def("valid", &transaction_t::valid) - ; - - class_< account_t > - ("Account", - init >() - [with_custodian_and_ward<1, 2>()]) - .def(self == self) - .def(self != self) - - .def(self_ns::str(self)) - - .def("__len__", accounts_len) - .def("__getitem__", accounts_getitem, return_internal_reference<1>()) - - .add_property("journal", - make_getter(&account_t::journal, - return_value_policy())) - .add_property("parent", - make_getter(&account_t::parent, - return_value_policy())) - .def_readwrite("name", &account_t::name) - .def_readwrite("note", &account_t::note) - .def_readonly("depth", &account_t::depth) - .add_property("data", py_account_get_data, py_account_set_data) - .def_readonly("ident", &account_t::ident) - - .def("fullname", &account_t::fullname) - - .def("add_account", &account_t::add_account) - .def("remove_account", &account_t::remove_account) - - .def("find_account", &account_t::find_account, - return_value_policy()) - - .def("valid", &account_t::valid) - ; - - class_< journal_t > ("Journal") - .def(self == self) - .def(self != self) - - .def("__len__", entries_len) - .def("__getitem__", entries_getitem, return_internal_reference<1>()) - - .add_property("master", make_getter(&journal_t::master, - return_internal_reference<1>())) - .add_property("basket", make_getter(&journal_t::basket, - return_internal_reference<1>())) - - .def_readonly("sources", &journal_t::sources) - - .def_readwrite("price_db", &journal_t::price_db) - - .def("add_account", &journal_t::add_account) - .def("remove_account", &journal_t::remove_account) - - .def("find_account", py_find_account_1, return_internal_reference<1>()) - .def("find_account", py_find_account_2, return_internal_reference<1>()) - .def("find_account_re", &journal_t::find_account_re, - return_internal_reference<1>()) - - .def("add_entry", py_add_entry) - .def("remove_entry", &journal_t::remove_entry) - - .def("add_entry_finalizer", py_add_entry_finalizer) - .def("remove_entry_finalizer", py_remove_entry_finalizer) - .def("run_entry_finalizers", py_run_entry_finalizers) - - .def("valid", &journal_t::valid) - ; - - class_< entry_base_t, entry_base_wrap, boost::noncopyable > ("EntryBase") - .def("__len__", transactions_len) - .def("__getitem__", transactions_getitem, - return_internal_reference<1>()) - - .def_readonly("journal", &entry_base_t::journal) - - .def_readonly("src_idx", &entry_base_t::src_idx) - .def_readonly("beg_pos", &entry_base_t::beg_pos) - .def_readonly("beg_line", &entry_base_t::beg_line) - .def_readonly("end_pos", &entry_base_t::end_pos) - .def_readonly("end_line", &entry_base_t::end_line) - - .def("add_transaction", py_add_transaction) - .def("remove_transaction", &entry_base_t::remove_transaction) - - .def(self == self) - .def(self != self) - - .def("finalize", &entry_base_t::finalize) - .def("valid", &entry_base_t::valid) - ; - - class_< entry_t, bases > ("Entry") - .add_property("date", &entry_t::date) - .add_property("effective_date", &entry_t::effective_date) - .add_property("actual_date", &entry_t::actual_date) - - .def_readwrite("code", &entry_t::code) - .def_readwrite("payee", &entry_t::payee) - - .add_property("state", &py_entry_state) - - .def("valid", &entry_t::valid) - ; - -#define EXC_TRANSLATE(type) \ - register_exception_translator(&exc_translate_ ## type); - - EXC_TRANSLATE(balance_error); - EXC_TRANSLATE(interval_expr_error); - EXC_TRANSLATE(format_error); - EXC_TRANSLATE(parse_error); -} diff --git a/py_option.cc b/py_option.cc deleted file mode 100644 index 877d92a7..00000000 --- a/py_option.cc +++ /dev/null @@ -1,73 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -struct py_option_t : public option_t -{ - PyObject * self; - - py_option_t(PyObject * self_, - const string& long_opt, - const bool wants_arg) - : self(self_), option_t(long_opt, wants_arg) {} - - virtual ~py_option_t() {} - - virtual bool check(option_source_t source) { - return call_method(self, "check", source); - } - - virtual void select(report_t * report, const char * optarg = NULL) { - if (optarg) - return call_method(self, "select", report, optarg); - else - return call_method(self, "select", report); - } -}; - -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(option_select_overloads, - py_option_t::select, 1, 2) - -typedef std::map options_map; -typedef std::pair options_pair; - -options_map options; - -static option_t * find_option(const string& name) -{ - options_map::const_iterator i = options.find(name); - if (i != options.end()) - return extract((*i).second.ptr()); - - return NULL; -} - -void shutdown_option() -{ - options.clear(); -} - -void export_option() -{ - class_< option_t, py_option_t, boost::noncopyable > - ("Option", init()) - .def_readonly("long_opt", &py_option_t::long_opt) - .def_readonly("short_opt", &py_option_t::short_opt) - .def_readonly("wants_arg", &py_option_t::wants_arg) - .def_readwrite("handled", &py_option_t::handled) - .def("check", &py_option_t::check) - .def("select", &py_option_t::select, option_select_overloads()) - ; - - enum_< option_t::option_source_t > ("OptionSource") - .value("InitFile", option_t::INIT_FILE) - .value("Environment", option_t::ENVIRONMENT) - .value("DataFile", option_t::DATA_FILE) - .value("CommandLine", option_t::COMMAND_LINE) - ; - - class_< options_map > ("OptionsMap") - .def(map_indexing_suite()) - ; - - scope().attr("options") = ptr(&options); -} diff --git a/py_parser.cc b/py_parser.cc deleted file mode 100644 index f119a0ef..00000000 --- a/py_parser.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "parser.h" - -#if 0 -#ifdef USE_BOOST_PYTHON - -using namespace boost::python; -using namespace ledger; - -struct py_parser_t : public parser_t -{ - PyObject * self; - py_parser_t(PyObject * self_) : self(self_) {} - - virtual bool test(std::istream& in) const { - return call_method(self, "test", in); - } - - virtual repitem_t * parse(std::istream& in, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL) { - return call_method(self, "parse", in, journal, master, - original_file); - } -}; - -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(parser_parse_overloads, - py_parser_t::parse, 2, 4) - -BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4) -BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads, - parse_journal_file, 2, 4) - -void export_parser() { - class_< parser_t, py_parser_t, boost::noncopyable > ("Parser") - .def("test", &py_parser_t::test) - .def("parse", &py_parser_t::parse, parser_parse_overloads()) - ; - - def("register_parser", register_parser); - def("unregister_parser", unregister_parser); - - def("parse_journal", parse_journal, parse_journal_overloads()); - def("parse_journal_file", parse_journal_file, parse_journal_file_overloads()); -} - -#endif // USE_BOOST_PYTHON -#endif diff --git a/py_report.cc b/py_report.cc deleted file mode 100644 index 0bc36857..00000000 --- a/py_report.cc +++ /dev/null @@ -1,13 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -void export_report() -{ - class_< report_t > ("Report") - .add_property("session", - make_getter(&report_t::session, - return_value_policy())) - - .def("apply_transforms", &report_t::apply_transforms) - ; -} diff --git a/py_session.cc b/py_session.cc deleted file mode 100644 index f249c54e..00000000 --- a/py_session.cc +++ /dev/null @@ -1,36 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -void export_session() -{ - class_< session_t > ("Session") - .def_readwrite("init_file", &session_t::init_file) - .def_readwrite("data_file", &session_t::data_file) - .def_readwrite("cache_file", &session_t::cache_file) - .def_readwrite("price_db", &session_t::price_db) - - .def_readwrite("balance_format", &session_t::balance_format) - .def_readwrite("register_format", &session_t::register_format) - .def_readwrite("wide_register_format", &session_t::wide_register_format) - .def_readwrite("plot_amount_format", &session_t::plot_amount_format) - .def_readwrite("plot_total_format", &session_t::plot_total_format) - .def_readwrite("print_format", &session_t::print_format) - .def_readwrite("write_hdr_format", &session_t::write_hdr_format) - .def_readwrite("write_xact_format", &session_t::write_xact_format) - .def_readwrite("equity_format", &session_t::equity_format) - .def_readwrite("prices_format", &session_t::prices_format) - .def_readwrite("pricesdb_format", &session_t::pricesdb_format) - - .def_readwrite("pricing_leeway", &session_t::pricing_leeway) - - .def_readwrite("download_quotes", &session_t::download_quotes) - .def_readwrite("use_cache", &session_t::use_cache) - .def_readwrite("cache_dirty", &session_t::cache_dirty) - .def_readwrite("debug_mode", &session_t::debug_mode) - .def_readwrite("verbose_mode", &session_t::verbose_mode) - .def_readwrite("trace_alloc_mode", &session_t::trace_alloc_mode) - .def_readwrite("trace_class_mode", &session_t::trace_class_mode) - - .def_readwrite("journals", &session_t::journals) - ; -} diff --git a/py_transform.cc b/py_transform.cc deleted file mode 100644 index a0ba31d4..00000000 --- a/py_transform.cc +++ /dev/null @@ -1,8 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -void export_transform() -{ - class_< repitem_t > ("Transform") - ; -} diff --git a/py_value.cc b/py_value.cc deleted file mode 100644 index 1a43ebc1..00000000 --- a/py_value.cc +++ /dev/null @@ -1,337 +0,0 @@ -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& val) -{ - switch (val.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 *) val.data)); - - case value_t::BALANCE_PAIR: - return balance_pair_len(*((balance_pair_t *) val.data)); - - case value_t::STRING: - case value_t::XML_NODE: - case value_t::POINTER: - return 1; - - case value_t::SEQUENCE: - return (*(value_t::sequence_t **) val.data)->size(); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -amount_t value_getitem(value_t& val, int i) -{ - std::size_t len = value_len(val); - - if (abs(i) >= len) { - PyErr_SetString(PyExc_IndexError, "Index out of range"); - throw_error_already_set(); - } - - switch (val.type) { - case value_t::BOOLEAN: - throw_(value_exception, "Cannot cast a boolean to an amount"); - - case value_t::INTEGER: - return long(val); - - case value_t::DATETIME: - throw_(value_exception, "Cannot cast a date/time to an amount"); - - case value_t::AMOUNT: - return *((amount_t *) val.data); - - case value_t::BALANCE: - return balance_getitem(*((balance_t *) val.data), i); - - case value_t::BALANCE_PAIR: - return balance_pair_getitem(*((balance_pair_t *) val.data), i); - - case value_t::STRING: - throw_(value_exception, "Cannot cast a string to an amount"); - - case value_t::XML_NODE: - return (*(xml::node_t **) data)->to_value(); - - case value_t::POINTER: - throw_(value_exception, "Cannot cast a pointer to an amount"); - - case value_t::SEQUENCE: - return (*(value_t::sequence_t **) val.data)[i]; - - default: - assert(0); - break; - } - assert(0); - return 0L; -} - -double py_to_float(value_t& val) -{ - return double(val); -} - -void export_value() -{ - class_< value_t > ("value") - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(init()) - .def(initmoment_t()) - - .def(self + self) - .def(self + other()) - .def(self + other()) - .def(self + other()) - .def(self + other()) - .def(self + long()) - .def(self + double()) - - .def(other() + self) - .def(other() + self) - .def(other() + self) - .def(other() + self) - .def(long() + self) - .def(double() + self) - - .def(self - self) - .def(self - other()) - .def(self - other()) - .def(self - other()) - .def(self - other()) - .def(self - long()) - .def(self - double()) - - .def(other() - self) - .def(other() - self) - .def(other() - self) - .def(other() - self) - .def(long() - self) - .def(double() - self) - - .def(self * self) - .def(self * other()) - .def(self * other()) - .def(self * other()) - .def(self * other()) - .def(self * long()) - .def(self * double()) - - .def(other() * self) - .def(other() * self) - .def(other() * self) - .def(other() * self) - .def(long() * self) - .def(double() * self) - - .def(self / self) - .def(self / other()) - .def(self / other()) - .def(self / other()) - .def(self / other()) - .def(self / long()) - .def(self / double()) - - .def(other() / self) - .def(other() / self) - .def(other() / self) - .def(other() / self) - .def(long() / self) - .def(double() / self) - - .def(- self) - - .def(self += self) - .def(self += other()) - .def(self += other()) - .def(self += other()) - .def(self += other()) - .def(self += long()) - .def(self += double()) - - .def(self -= self) - .def(self -= other()) - .def(self -= other()) - .def(self -= other()) - .def(self -= other()) - .def(self -= long()) - .def(self -= double()) - - .def(self *= self) - .def(self *= other()) - .def(self *= other()) - .def(self *= other()) - .def(self *= other()) - .def(self *= long()) - .def(self *= double()) - - .def(self /= self) - .def(self /= other()) - .def(self /= other()) - .def(self /= other()) - .def(self /= other()) - .def(self /= long()) - .def(self /= double()) - - .def(self < self) - .def(self < other()) - .def(self < other()) - .def(self < other()) - .def(self < other()) - .def(self < long()) - .def(self < othermoment_t()) - .def(self < double()) - - .def(other() < self) - .def(other() < self) - .def(other() < self) - .def(other() < self) - .def(long() < self) - .def(othermoment_t() < self) - .def(double() < self) - - .def(self <= self) - .def(self <= other()) - .def(self <= other()) - .def(self <= other()) - .def(self <= other()) - .def(self <= long()) - .def(self <= othermoment_t()) - .def(self <= double()) - - .def(other() <= self) - .def(other() <= self) - .def(other() <= self) - .def(other() <= self) - .def(long() <= self) - .def(othermoment_t() <= self) - .def(double() <= self) - - .def(self > self) - .def(self > other()) - .def(self > other()) - .def(self > other()) - .def(self > other()) - .def(self > long()) - .def(self > othermoment_t()) - .def(self > double()) - - .def(other() > self) - .def(other() > self) - .def(other() > self) - .def(other() > self) - .def(long() > self) - .def(othermoment_t() > self) - .def(double() > self) - - .def(self >= self) - .def(self >= other()) - .def(self >= other()) - .def(self >= other()) - .def(self >= other()) - .def(self >= long()) - .def(self >= othermoment_t()) - .def(self >= double()) - - .def(other() >= self) - .def(other() >= self) - .def(other() >= self) - .def(other() >= self) - .def(long() >= self) - .def(othermoment_t() >= self) - .def(double() >= self) - - .def(self == self) - .def(self == other()) - .def(self == other()) - .def(self == other()) - .def(self == other()) - .def(self == long()) - .def(self == othermoment_t()) - .def(self == double()) - - .def(other() == self) - .def(other() == self) - .def(other() == self) - .def(other() == self) - .def(long() == self) - .def(othermoment_t() == self) - .def(double() == self) - - .def(self != self) - .def(self != other()) - .def(self != other()) - .def(self != other()) - .def(self != other()) - .def(self != long()) - .def(self != othermoment_t()) - .def(self != double()) - - .def(other() != self) - .def(other() != self) - .def(other() != self) - .def(other() != self) - .def(long() != self) - .def(othermoment_t() != self) - .def(double() != self) - - .def(! self) - - .def(self_ns::int_(self)) - .def(self_ns::float_(self)) - .def(self_ns::str(self)) - - .def_readonly("type", &value_t::type) - - .def("__abs__", &value_t::abs) - .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("write", &value_t::write) - ; - - 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("BalancePair", value_t::BALANCE_PAIR) - .value("String", value_t::STRING) - .value("XmlNode", value_t::XML_NODE) - .value("Pointer", value_t::POINTER) - .value("Sequence", value_t::SEQUENCE) - ; -} diff --git a/py_xpath.cc b/py_xpath.cc deleted file mode 100644 index 6569ce7b..00000000 --- a/py_xpath.cc +++ /dev/null @@ -1,79 +0,0 @@ -using namespace boost::python; -using namespace ledger; - -value_t py_calc_1(xpath_t::op_t& xpath_t, const details_t& item) -{ - value_t result; - xpath_t.calc(result, item); - return result; -} - -template -value_t py_calc(xpath_t::op_t& xpath_t, const T& item) -{ - value_t result; - xpath_t.calc(result, details_t(item)); - return result; -} - -xpath_t::op_t * py_parse_xpath_t_1(const string& str) -{ - return parse_xpath_t(str); -} - -#define EXC_TRANSLATOR(type) \ - void exc_translate_ ## type(const type& err) { \ - PyErr_SetString(PyExc_RuntimeError, err.what()); \ - } - -EXC_TRANSLATOR(xpath_t_error) -EXC_TRANSLATOR(calc_error) -#if 0 -EXC_TRANSLATOR(mask_error) -#endif - -void export_xpath() -{ - class_< details_t > ("Details", init()) - .def(init()) - .def(init()) - .add_property("entry", - make_getter(&details_t::entry, - return_value_policy())) - .add_property("xact", - make_getter(&details_t::xact, - return_value_policy())) - .add_property("account", - make_getter(&details_t::account, - return_value_policy())) - ; - - class_< xpath_t::op_t > ("ValueExpr", init()) - .def("calc", py_calc_1) - .def("calc", py_calc) - .def("calc", py_calc) - .def("calc", py_calc) - ; - - def("parse_xpath_t", py_parse_xpath_t_1, - return_value_policy()); - - class_< item_predicate > - ("TransactionPredicate", init()) - .def("__call__", &item_predicate::operator()) - ; - - class_< item_predicate > - ("AccountPredicate", init()) - .def("__call__", &item_predicate::operator()) - ; - -#define EXC_TRANSLATE(type) \ - register_exception_translator(&exc_translate_ ## type); - - EXC_TRANSLATE(xpath_t_error); - EXC_TRANSLATE(calc_error); -#if 0 - EXC_TRANSLATE(mask_error); -#endif -} diff --git a/pyfstream.h b/pyfstream.h deleted file mode 100644 index 27e5166b..00000000 --- a/pyfstream.h +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef _PYFSTREAM_H -#define _PYFSTREAM_H - -// pyofstream -// - a stream that writes on a Python file object - -class pyoutbuf : public std::streambuf { - protected: - PyFileObject * fo; // Python file object - public: - // constructor - pyoutbuf (PyFileObject * _fo) : fo(_fo) {} - - 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, (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, (PyObject *)fo) < 0) - num = 0; - delete[] buf; - return num; - } -}; - -class pyofstream : public std::ostream { - protected: - pyoutbuf buf; - public: - pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { - rdbuf(&buf); - } -}; - -// pyifstream -// - a stream that reads on a file descriptor - -class pyinbuf : public std::streambuf { - 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) { - setg (buffer+pbSize, // beginning of putback area - buffer+pbSize, // read position - buffer+pbSize); // end position - } - - 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((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 std::istream { - protected: - pyinbuf buf; - public: - pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { - rdbuf(&buf); - } -}; - -#endif // _PYFSTREAM_H diff --git a/pyledger.cc b/pyledger.cc deleted file mode 100644 index 08b69590..00000000 --- a/pyledger.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include "py_eval.h" -#include "session.h" - -using namespace boost::python; - -BOOST_PYTHON_MODULE(ledger) -{ - ledger::initialize(); - ledger::initialize_for_python(); -} diff --git a/pyledger.h b/pyledger.h deleted file mode 100644 index 9d8cafdf..00000000 --- a/pyledger.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _PYLEDGER_H -#define _PYLEDGER_H - -////////////////////////////////////////////////////////////////////// -// -// Ledger Accounting Tool (with Python support via Boost.Python) -// -// A command-line tool for general double-entry accounting. -// -// Copyright (c) 2003,2004 John Wiegley -// - -#include -#include - -#endif // _PYLEDGER_H diff --git a/qif.cc b/qif.cc deleted file mode 100644 index 5e567ea0..00000000 --- a/qif.cc +++ /dev/null @@ -1,243 +0,0 @@ -#include "qif.h" -#include "journal.h" - -namespace ledger { - -#define MAX_LINE 1024 - -static char line[MAX_LINE + 1]; -static string path; -static unsigned int src_idx; -static unsigned int linenum; - -static inline char * get_line(std::istream& in) { - in.getline(line, MAX_LINE); - int len = std::strlen(line); - if (line[len - 1] == '\r') - line[len - 1] = '\0'; - linenum++; - return line; -} - -bool qif_parser_t::test(std::istream& in) const -{ - char magic[sizeof(unsigned int) + 1]; - in.read(magic, sizeof(unsigned int)); - magic[sizeof(unsigned int)] = '\0'; - in.clear(); - in.seekg(0, std::ios::beg); - - return (std::strcmp(magic, "!Typ") == 0 || - std::strcmp(magic, "\n!Ty") == 0 || - std::strcmp(magic, "\r\n!T") == 0); -} - -unsigned int qif_parser_t::parse(std::istream& in, - journal_t * journal, - account_t * master, - const string *) -{ - std::auto_ptr entry; - std::auto_ptr 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; - - entry.reset(new entry_t); - xact = new transaction_t(master); - entry->add_transaction(xact); - - path = journal->sources.back(); - src_idx = journal->sources.size() - 1; - linenum = 1; - - unsigned long beg_pos = 0; - unsigned long beg_line = 0; - -#define SET_BEG_POS_AND_LINE() \ - if (! beg_line) { \ - beg_pos = in.tellg(); \ - beg_line = linenum; \ - } - - while (in.good() && ! in.eof()) { - char c; - in.get(c); - switch (c) { - case ' ': - case '\t': - if (peek_next_nonws(in) != '\n') { - get_line(in); - throw_(parse_exception, "Line begins with whitespace"); - } - // fall through... - - case '\n': - linenum++; - case '\r': // skip blank lines - break; - - case '!': - get_line(in); - - if (std::strcmp(line, "Type:Invst") == 0 || - std::strcmp(line, "Account") == 0 || - std::strcmp(line, "Type:Cat") == 0 || - std::strcmp(line, "Type:Class") == 0 || - std::strcmp(line, "Type:Memorized") == 0) - throw_(parse_exception, - "QIF files of type " << line << " are not supported."); - break; - - case 'D': - SET_BEG_POS_AND_LINE(); - get_line(in); - entry->_date = parse_datetime(line); - break; - - case 'T': - case '$': { - SET_BEG_POS_AND_LINE(); - get_line(in); - xact->amount.parse(line); - - unsigned char flags = xact->amount.commodity().flags(); - unsigned char prec = xact->amount.commodity().precision(); - - if (! def_commodity) { - def_commodity = commodity_t::find_or_create("$"); - assert(def_commodity); - } - xact->amount.set_commodity(*def_commodity); - - def_commodity->add_flags(flags); - if (prec > def_commodity->precision()) - def_commodity->set_precision(prec); - - if (c == '$') { - saw_splits = true; - xact->amount.in_place_negate(); - } else { - total = xact; - } - break; - } - - case 'C': - SET_BEG_POS_AND_LINE(); - c = in.peek(); - if (c == '*' || c == 'X') { - in.get(c); - xact->state = transaction_t::CLEARED; - } - break; - - case 'N': - SET_BEG_POS_AND_LINE(); - get_line(in); - entry->code = line; - break; - - case 'P': - case 'M': - case 'L': - case 'S': - case 'E': { - SET_BEG_POS_AND_LINE(); - get_line(in); - - switch (c) { - case 'P': - entry->payee = line; - break; - - case 'S': - xact = new transaction_t(NULL); - entry->add_transaction(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] == '[' ? - line + 1 : line); - if (c == 'L') - saw_category = true; - break; - } - - case 'M': - case 'E': - xact->note = line; - break; - } - break; - } - - case 'A': - SET_BEG_POS_AND_LINE(); - // jww (2004-08-19): these are ignored right now - get_line(in); - break; - - case '^': { - account_t * other; - if (xact->account == master) { - if (! misc) - misc = journal->find_account("Miscellaneous"); - other = misc; - } else { - other = master; - } - - if (total && saw_category) { - if (! saw_splits) - total->amount.in_place_negate(); // negate, to show correct flow - else - total->account = other; - } - - if (! saw_splits) { - transaction_t * nxact = new transaction_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); - } - - if (journal->add_entry(entry.get())) { - entry->src_idx = src_idx; - entry->beg_pos = beg_pos; - entry->beg_line = beg_line; - entry->end_pos = in.tellg(); - entry->end_line = linenum; - entry.release(); - count++; - } - - // reset things for the next entry - entry.reset(new entry_t); - xact = new transaction_t(master); - entry->add_transaction(xact); - - saw_splits = false; - saw_category = false; - total = NULL; - beg_line = 0; - break; - } - - default: - get_line(in); - break; - } - } - - return count; -} - -} // namespace ledger diff --git a/qif.h b/qif.h deleted file mode 100644 index 6c68a99b..00000000 --- a/qif.h +++ /dev/null @@ -1,21 +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, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); -}; - -} // namespace ledger - -#endif // _QIF_H diff --git a/quotes.cc b/quotes.cc deleted file mode 100644 index 1463c9bc..00000000 --- a/quotes.cc +++ /dev/null @@ -1,80 +0,0 @@ -#include "quotes.h" - -namespace ledger { - -void quotes_by_script::operator()(commodity_base_t& commodity, - const ptime& moment, - const ptime& date, - const ptime& last, - amount_t& price) -{ - LOGGER("quotes.download"); - - DEBUG("commodity: " << commodity.symbol); - DEBUG(" now: " << now); - DEBUG(" moment: " << moment); - DEBUG(" date: " << date); - DEBUG(" last: " << last); - - if (SHOW_DEBUG() && commodity.history) - DEBUG("last_lookup: " << commodity.history->last_lookup); - DEBUG("pricing_leeway is " << pricing_leeway); - - if ((commodity.history && - (time_now - commodity.history->last_lookup) < pricing_leeway) || - (time_now - last) < pricing_leeway || - (price && moment > date && (moment - date) <= pricing_leeway)) - return; - - 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(now, price); - - commodity.history->last_lookup = time_now; - cache_dirty = true; - - if (price && ! price_db.empty()) { -#if defined(__GNUG__) && __GNUG__ < 3 - std::ofstream database(price_db.c_str(), ios::out | ios::app); -#else - std::ofstream database(price_db.c_str(), - std::ios_base::out | std::ios_base::app); -#endif -#if 0 - // jww (2007-04-18): Need to convert to local time and print - // here, print with UTC timezone specifier - database << "P " << now.to_string("%Y/%m/%d %H:%M:%S") - << " " << commodity.symbol << " " << price << endl; -#endif - } - } else { - throw exception(string("Failed to download price for '") + - commodity.symbol + "' (command: \"getquote " + - commodity.symbol + "\")", - context()); - } -} - -} // namespace ledger diff --git a/quotes.h b/quotes.h deleted file mode 100644 index a1fabd93..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 -{ - string price_db; - time_duration pricing_leeway; - bool& cache_dirty; - - public: - quotes_by_script(string _price_db, - time_duration _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 ptime& moment, - const ptime& date, - const ptime& last, - amount_t& price); -}; - -} // namespace ledger - -#endif // _QUOTES_H diff --git a/reconcile.cc b/reconcile.cc deleted file mode 100644 index e69de29b..00000000 diff --git a/reconcile.h b/reconcile.h deleted file mode 100644 index e69de29b..00000000 diff --git a/register.cc b/register.cc deleted file mode 100644 index 9f33bd0c..00000000 --- a/register.cc +++ /dev/null @@ -1,166 +0,0 @@ -#include "register.h" -#include "journal.h" - -namespace ledger { - -string abbreviate(const string& str, - unsigned int width, - elision_style_t elision_style, - const bool is_account, - int abbrev_length) -{ - const unsigned int len = str.length(); - if (len <= width) - return str; - - assert(width < 4095); - - static 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 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::iterator i = parts.begin(); - i != parts.end(); - i++) { - // Don't contract the last element - std::list::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; -} - -static void scan_for_transactions(std::ostream& out, const xml::node_t * node) -{ - if (! (node->flags & XML_NODE_IS_PARENT)) - return; - - const xml::parent_node_t * parent = - static_cast(node); - - for (const xml::node_t * child = parent->children(); - child; - child = child->next) - if (child->name_id == xml::document_t::TRANSACTION) { - const xml::transaction_node_t * xact_node = - dynamic_cast(child); - assert(xact_node); - - const transaction_t * xact = xact_node->transaction; - assert(xact); - - out << xact->entry->date() << ' ' - << std::setw(21) << std::left - << abbreviate(xact->entry->payee, 21) << ' ' - << std::setw(21) << std::left - << abbreviate(xact->account->fullname(), 21, - ABBREVIATE, true) << ' ' - << std::setw(12) << std::right - << xact->amount << '\n'; - } else { - scan_for_transactions(out, child); - } -} - -void register_command::print_document(std::ostream& out, - xml::document_t * doc) -{ -#if 1 - scan_for_transactions(out, doc->top); - out.flush(); -#else - value_t nodelist; - xml::xpath_t::eval(nodelist, "//transaction", doc); - - const value_t::sequence_t * xact_list = nodelist.to_sequence(); - assert(xact_list); - - for (value_t::sequence_t::const_iterator i = xact_list->begin(); - i != xact_list->end(); - i++) { - const xml::node_t * node = (*i).to_xml_node(); - assert(node); - - const xml::transaction_node_t * xact_node = - dynamic_cast(node); - assert(xact_node); - - const transaction_t * xact = xact_node->transaction; - assert(xact); - - std::cout << xact->entry->date() << ' ' - << std::setw(21) << std::left - << abbreviate(xact->entry->payee, 21) << ' ' - << std::setw(21) << std::left - << abbreviate(xact->account->fullname(), 21, - ABBREVIATE, true) << ' ' - << std::setw(12) << std::right - << xact->amount - << std::endl; - } -#endif -} - -} // namespace ledger diff --git a/register.h b/register.h deleted file mode 100644 index ba2020ec..00000000 --- a/register.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef _REGISTER_H -#define _REGISTER_H - -#include "xpath.h" - -namespace ledger { - -class register_command : public xml::xpath_t::functor_t -{ - public: - register_command() : xml::xpath_t::functor_t("register") {} - - virtual void operator()(value_t&, xml::xpath_t::scope_t * locals) { - std::ostream * out = get_ptr(locals, 0); - xml::document_t * doc = get_ptr(locals, 1); - - print_document(*out, doc); - } - - virtual void print_document(std::ostream& out, xml::document_t * doc); -}; - -enum elision_style_t { - TRUNCATE_TRAILING, - TRUNCATE_MIDDLE, - TRUNCATE_LEADING, - ABBREVIATE -}; - -string abbreviate(const string& str, - unsigned int width, - elision_style_t elision_style = TRUNCATE_TRAILING, - const bool is_account = false, - int abbrev_length = 2); - -} // namespace ledger - -#endif // _REGISTER_H diff --git a/report.cc b/report.cc deleted file mode 100644 index 116747ef..00000000 --- a/report.cc +++ /dev/null @@ -1,189 +0,0 @@ -#include "report.h" - -namespace ledger { - -report_t::~report_t() -{ - TRACE_DTOR(report_t); - for (std::list::const_iterator i = transforms.begin(); - i != transforms.end(); - i++) - delete *i; -} - -void report_t::apply_transforms(xml::document_t * document) -{ - for (std::list::const_iterator i = transforms.begin(); - i != transforms.end(); - i++) - (*i)->execute(document); -} - -void report_t::abbrev(value_t& result, xml::xpath_t::scope_t * locals) -{ - if (locals->args.size() < 2) - throw_(exception, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); - - string str = locals->args[0].to_string(); - long wid = locals->args[1]; - - elision_style_t style = session->elision_style; - if (locals->args.size() == 3) - style = (elision_style_t)locals->args[2].to_integer(); - - long abbrev_len = session->abbrev_length; - if (locals->args.size() == 4) - abbrev_len = locals->args[3].to_integer(); - - result.set_string(abbreviate(str, wid, style, true, (int)abbrev_len)); -} - -void report_t::ftime(value_t&, xml::xpath_t::scope_t * locals) -{ - if (locals->args.size() < 1) - throw_(exception, "usage: ftime(DATE [, DATE_FORMAT])"); - - moment_t date = locals->args[0].to_datetime(); - - string date_format; - if (locals->args.size() == 2) - date_format = locals->args[1].to_string(); -#if 0 - // jww (2007-04-18): Need to setup an output facet here - else - date_format = moment_t::output_format; - - result.set_string(date.to_string(date_format)); -#endif -} - -bool report_t::resolve(const string& name, value_t& result, - xml::xpath_t::scope_t * locals) -{ - const char * p = name.c_str(); - switch (*p) { - case 'a': - if (name == "abbrev") { - abbrev(result, locals); - return true; - } - break; - - case 'f': - if (name == "ftime") { - ftime(result, locals); - return true; - } - break; - } - - return xml::xpath_t::scope_t::resolve(name, result, locals); -} - -xml::xpath_t::op_t * report_t::lookup(const string& name) -{ - const char * p = name.c_str(); - switch (*p) { - case 'o': - if (std::strncmp(p, "option_", 7) == 0) { - p = p + 7; - switch (*p) { - case 'a': -#if 0 - if (std::strcmp(p, "accounts") == 0) - return MAKE_FUNCTOR(report_t, option_accounts); - else -#endif - if (std::strcmp(p, "amount") == 0) - return MAKE_FUNCTOR(report_t, option_amount); - break; - - case 'b': - if (std::strcmp(p, "bar") == 0) - return MAKE_FUNCTOR(report_t, option_bar); - break; - -#if 0 - case 'c': - if (std::strcmp(p, "clean") == 0) - return MAKE_FUNCTOR(report_t, option_clean); - else if (std::strcmp(p, "compact") == 0) - return MAKE_FUNCTOR(report_t, option_compact); - break; -#endif - - case 'e': -#if 0 - if (std::strcmp(p, "entries") == 0) - return MAKE_FUNCTOR(report_t, option_entries); - else if (std::strcmp(p, "eval") == 0) - return MAKE_FUNCTOR(report_t, option_eval); - else if (std::strcmp(p, "exclude") == 0) - return MAKE_FUNCTOR(report_t, option_remove); -#endif - break; - - case 'f': - if (std::strcmp(p, "foo") == 0) - return MAKE_FUNCTOR(report_t, option_foo); - else if (std::strcmp(p, "format") == 0) - return MAKE_FUNCTOR(report_t, option_format); - break; - - case 'i': -#if 0 - if (std::strcmp(p, "include") == 0) - return MAKE_FUNCTOR(report_t, option_select); -#endif - break; - - case 'l': -#if 0 - if (! *(p + 1) || std::strcmp(p, "limit") == 0) - return MAKE_FUNCTOR(report_t, option_limit); -#endif - break; - -#if 0 - case 'm': - if (std::strcmp(p, "merge") == 0) - return MAKE_FUNCTOR(report_t, option_merge); - break; -#endif - - case 'r': -#if 0 - if (std::strcmp(p, "remove") == 0) - return MAKE_FUNCTOR(report_t, option_remove); -#endif - break; - -#if 0 - case 's': - if (std::strcmp(p, "select") == 0) - return MAKE_FUNCTOR(report_t, option_select); - else if (std::strcmp(p, "split") == 0) - return MAKE_FUNCTOR(report_t, option_split); - break; -#endif - - case 't': - if (! *(p + 1)) - 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 (! *(p + 1)) - return MAKE_FUNCTOR(report_t, option_total); - break; - } - } - break; - } - - return xml::xpath_t::scope_t::lookup(name); -} - -} // namespace ledger diff --git a/report.h b/report.h deleted file mode 100644 index 11a0b759..00000000 --- a/report.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef _REPORT_H -#define _REPORT_H - -#include "session.h" -#include "transform.h" - -namespace ledger { - -typedef std::list strings_list; - -class report_t : public xml::xpath_t::scope_t -{ - public: - string output_file; - string format_string; - string amount_expr; - string total_expr; - string date_output_format; - - unsigned long budget_flags; - - string account; - string pager; - - bool show_totals; - bool raw_mode; - - session_t * session; - transform_t * last_transform; - - std::list transforms; - - report_t(session_t * _session) - : xml::xpath_t::scope_t(_session), - show_totals(false), - raw_mode(false), - session(_session), - last_transform(NULL) - { - TRACE_CTOR(report_t, "session_t *"); - eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); - } - - virtual ~report_t(); - - void apply_transforms(xml::document_t * document); - - // - // Utility functions for value expressions - // - - void ftime(value_t& result, xml::xpath_t::scope_t * locals); - void abbrev(value_t& result, xml::xpath_t::scope_t * locals); - - // - // Config options - // - - void eval(const string& expr) { - xml::xpath_t(expr).compile((xml::document_t *)NULL, this); - } - void option_eval(value_t&, xml::xpath_t::scope_t * locals) { - eval(locals->args[0].to_string()); - } - - void option_amount(value_t&, xml::xpath_t::scope_t * locals) { - eval(string("t=") + locals->args[0].to_string()); - } - void option_total(value_t&, xml::xpath_t::scope_t * locals) { - eval(string("T()=") + locals->args[0].to_string()); - } - - void option_format(value_t&, xml::xpath_t::scope_t * locals) { - format_string = locals->args[0].to_string(); - } - - void option_raw(value_t&) { - raw_mode = true; - } - - void option_foo(value_t&) { - std::cout << "This is foo" << std::endl; - } - void option_bar(value_t&, xml::xpath_t::scope_t * locals) { - std::cout << "This is bar: " << locals->args[0] << std::endl; - } - - // - // Transform options - // - -#if 0 - void option_select(value_t&, xml::xpath_t::scope_t * locals) { - transforms.push_back(new select_transform(locals->args[0].to_string())); - } - void option_limit(value_t&, xml::xpath_t::scope_t * locals) { - string expr = (string("//xact[") + - locals->args[0].to_string() + "]"); - transforms.push_back(new select_transform(expr)); - } - - void option_remove(value_t&, xml::xpath_t::scope_t * locals) { - transforms.push_back(new remove_transform(locals->args[0].to_string())); - } - - void option_accounts(value_t&) { - transforms.push_back(new accounts_transform); - } - void option_compact(value_t&) { - transforms.push_back(new compact_transform); - } - void option_clean(value_t&) { - transforms.push_back(new clean_transform); - } - void option_entries(value_t&) { - transforms.push_back(new entries_transform); - } - - void option_split(value_t&) { - transforms.push_back(new split_transform); - } - void option_merge(value_t&) { - transforms.push_back(new merge_transform); - } -#endif - - // - // Scope members - // - - virtual bool resolve(const string& name, value_t& result, - xml::xpath_t::scope_t * locals); - virtual xml::xpath_t::op_t * lookup(const string& name); -}; - -string abbrev(const string& str, unsigned int width, - const bool is_account); - -} // namespace ledger - -#endif // _REPORT_H diff --git a/scantime.ll b/scantime.ll deleted file mode 100644 index 75819bcc..00000000 --- a/scantime.ll +++ /dev/null @@ -1,32 +0,0 @@ -%option c++ 8bit - -%{ -#define YYSTYPE struct ledger::intorchar - -extern int yywrap(); - -#include "times.h" -#include "parsetime.h" - -extern YYSTYPE yylval; -%} - -shortmon (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) -longmon (January|February|March|April|May|June|July|August|September|October|November|December) -ampm (AM|PM|am|pm|A.M.|P.M.|a.m.|p.m.|[AP]|[ap]) - -%% - -[ \t] return TOK_SPACE; -[\r\n] ; - -[0-9]{4} yylval = ledger::intorchar(std::atoi(yytext)); return TOK_FOURNUM; -[0-9]{2} yylval = ledger::intorchar(std::atoi(yytext)); return TOK_TWONUM; -[0-9]{1} yylval = ledger::intorchar(std::atoi(yytext)); return TOK_ONENUM; - -{shortmon} yylval = ledger::intorchar(yytext); return TOK_MONTH; -{longmon} yylval = ledger::intorchar(yytext); return TOK_MONTH; - -{ampm} yylval = ledger::intorchar(yytext); return TOK_AMPM; - -. return (int) yytext[0]; diff --git a/session.cc b/session.cc deleted file mode 100644 index 04ad3637..00000000 --- a/session.cc +++ /dev/null @@ -1,226 +0,0 @@ -#include "session.h" -#if defined(USE_BOOST_PYTHON) -#include "py_eval.h" -#endif - -namespace ledger { - -unsigned int session_t::read_journal(std::istream& in, - journal_t * journal, - account_t * master, - const string * original_file) -{ - if (! master) - master = journal->master; - - for (std::list::iterator i = parsers.begin(); - i != parsers.end(); - i++) - if ((*i)->test(in)) - return (*i)->parse(in, journal, master, original_file); - - return 0; -} - -unsigned int session_t::read_journal(const string& path, - journal_t * journal, - account_t * master, - const string * original_file) -{ - journal->sources.push_back(path); - - if (access(path.c_str(), R_OK) == -1) - throw_(exception, "Cannot read file '" << path << "'"); - - if (! original_file) - original_file = &path; - - std::ifstream stream(path.c_str()); - return read_journal(stream, journal, master, original_file); -} - -void session_t::read_init() -{ - if (init_file.empty()) - return; - - if (access(init_file.c_str(), R_OK) == -1) - throw_(exception, "Cannot read init file '" << init_file << "'"); - - std::ifstream init(init_file.c_str()); - - // jww (2006-09-15): Read initialization options here! -} - -journal_t * session_t::read_data(const string& master_account) -{ - TRACE_START(parser, 1, "Parsing journal file"); - - journal_t * journal = new_journal(); - journal->document = new xml::document_t; - journal->document->set_top(xml::wrap_node(journal->document, journal)); - - unsigned int entry_count = 0; - - DEBUG_("ledger.cache", "3. use_cache = " << use_cache); - - if (use_cache && ! cache_file.empty() && - ! data_file.empty()) { - DEBUG_("ledger.cache", "using_cache " << cache_file); - cache_dirty = true; - if (access(cache_file.c_str(), R_OK) != -1) { - std::ifstream stream(cache_file.c_str()); - - string price_db_orig = journal->price_db; - journal->price_db = price_db; - entry_count += read_journal(stream, journal, NULL, - &data_file); - if (entry_count > 0) - cache_dirty = false; - else - journal->price_db = price_db_orig; - } - } - - if (entry_count == 0 && ! data_file.empty()) { - account_t * acct = NULL; - if (! master_account.empty()) - acct = journal->find_account(master_account); - - journal->price_db = price_db; - if (! journal->price_db.empty() && - access(journal->price_db.c_str(), R_OK) != -1) { - if (read_journal(journal->price_db, journal)) { - throw_(exception, "Entries not allowed in price history file"); - } else { - DEBUG_("ledger.cache", "read price database " << journal->price_db); - journal->sources.pop_back(); - } - } - - DEBUG_("ledger.cache", "rejected cache, parsing " << data_file); - if (data_file == "-") { - use_cache = false; - journal->sources.push_back(""); - entry_count += read_journal(std::cin, journal, acct); - } - else if (access(data_file.c_str(), R_OK) != -1) { - entry_count += read_journal(data_file, journal, acct); - if (! journal->price_db.empty()) - journal->sources.push_back(journal->price_db); - } - } - - VERIFY(journal->valid()); - - if (entry_count == 0) - throw_(exception, "Failed to locate any journal entries; " - "did you specify a valid file with -f?"); - - TRACE_STOP(parser, 1); - - return journal; -} - -bool session_t::resolve(const string& name, value_t& result, - xml::xpath_t::scope_t * locals) -{ - const char * p = name.c_str(); - switch (*p) { - case 'd': - if (name == "date_format") { - // jww (2007-04-18): What to do here? -#if 0 - result.set_string(moment_t::output_format); -#endif - return true; - } - break; - - case 'n': - switch (*++p) { - case 'o': - if (name == "now") { - result = now; - return true; - } - break; - } - break; - - case 'r': - if (name == "register_format") { - result = register_format; - return true; - } - break; - } - - return xml::xpath_t::scope_t::resolve(name, result, locals); -} - -xml::xpath_t::op_t * session_t::lookup(const string& name) -{ - const char * p = name.c_str(); - switch (*p) { - case 'o': - if (std::strncmp(p, "option_", 7) == 0) { - p = p + 7; - switch (*p) { - case 'd': - if (std::strcmp(p, "debug") == 0) - return MAKE_FUNCTOR(session_t, option_debug); - break; - - case 'f': - if (! *(p + 1) || 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, "verify") == 0) - return MAKE_FUNCTOR(session_t, option_verify); - break; - } - } - break; - } - - return xml::xpath_t::scope_t::lookup(name); -} - -// jww (2007-04-26): All of Ledger should be accessed through a -// session_t object -void initialize() -{ - IF_VERIFY() - initialize_memory_tracing(); - - amount_t::initialize(); - xml::xpath_t::initialize(); -} - -void shutdown() -{ -#if defined(USE_BOOST_PYTHON) - shutdown_for_python(); -#endif - xml::xpath_t::shutdown(); - amount_t::shutdown(); - - IF_VERIFY() { - INFO("Ledger shutdown (Boost/libstdc++ may still hold memory)"); - shutdown_memory_tracing(); - } else { - INFO("Ledger shutdown"); - } -} - -} // namespace ledger diff --git a/session.h b/session.h deleted file mode 100644 index d2d52186..00000000 --- a/session.h +++ /dev/null @@ -1,197 +0,0 @@ -#ifndef _SESSION_H -#define _SESSION_H - -#include "journal.h" -#include "parser.h" -#include "register.h" - -namespace ledger { - -class session_t : public xml::xpath_t::scope_t -{ - public: - string init_file; - string data_file; - string cache_file; - string 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; - - moment_t now; - - elision_style_t elision_style; - - int abbrev_length; - - bool ansi_codes; - bool ansi_invert; - - std::list journals; - std::list parsers; - - session_t(xml::xpath_t::scope_t * _parent = NULL) : - xml::xpath_t::scope_t(_parent), - - register_format - ("%((//entry)%{date} %-.20{payee}" - "%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"), - wide_register_format - ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" - "%48|%-.38A %22.108t %!22.132T\n"), - print_format -#if 1 - ("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"), -#else - ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), -#endif - balance_format - ("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"), - equity_format - - ("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\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), - - elision_style(ABBREVIATE), - abbrev_length(2), - - ansi_codes(false), - ansi_invert(false) { - TRACE_CTOR(session_t, "xml::xpath_t::scope_t *"); - } - - virtual ~session_t() { - TRACE_DTOR(session_t); - - for (std::list::iterator i = journals.begin(); - i != journals.end(); - i++) - delete *i; - - for (std::list::iterator i = parsers.begin(); - i != parsers.end(); - i++) - delete *i; - } - - journal_t * new_journal() { - journal_t * journal = new journal_t(this); - journals.push_back(journal); - return journal; - } - void close_journal(journal_t * journal) { - journals.remove(journal); - delete journal; - } - - unsigned int read_journal(std::istream& in, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); - - unsigned int read_journal(const string& path, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); - - void read_init(); - - journal_t * read_data(const string& master_account = ""); - - void register_parser(parser_t * parser) { - parsers.push_back(parser); - } - bool unregister_parser(parser_t * parser) { - std::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; - } - - // - // Scope members - // - - virtual bool resolve(const string& name, value_t& result, - xml::xpath_t::scope_t * locals = NULL); - virtual xml::xpath_t::op_t * lookup(const string& name); - - // - // Debug options - // - - void option_verify(value_t&) {} - void option_trace(value_t&, xml::xpath_t::scope_t * locals) {} - void option_debug(value_t&, xml::xpath_t::scope_t * locals) {} - - void option_verbose(value_t&) { - if (_log_level < LOG_INFO) - _log_level = LOG_INFO; - } - - // - // Option handlers - // - - void option_file(value_t&, xml::xpath_t::scope_t * locals) { - data_file = locals->args.to_string(); - } - -#if 0 -#if defined(USE_BOOST_PYTHON) - void option_import(value_t&) { - python_import(optarg); - } - void option_import_stdin(value_t&) { - python_eval(std::cin, PY_EVAL_MULTI); - } -#endif -#endif -}; - -void initialize(); -void shutdown(); - -} // namespace ledger - -#endif // _SESSION_H diff --git a/setup.py b/setup.py index 47d47827..fd42d62d 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,6 @@ from distutils.core import setup, Extension import os -import string defines = [('PYTHON_MODULE', 1)] libs = os.environ["PYLIBS"].split() @@ -15,5 +14,6 @@ setup(name = "Ledger", author_email = "johnw@newartisans.com", url = "http://johnwiegley.com/", ext_modules = [ - Extension("ledger", [os.path.join(os.environ['SRCDIR'], "pyledger.cc")], + Extension("ledger", + [os.path.join(os.environ['SRCDIR'], "src", "pyledger.cc")], define_macros = defines, libraries = libs)]) diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..0d20b648 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/src/COPYRIGHT b/src/COPYRIGHT new file mode 100644 index 00000000..c9d4bd18 --- /dev/null +++ b/src/COPYRIGHT @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2003-2007, 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. + */ diff --git a/src/amount.cc b/src/amount.cc new file mode 100644 index 00000000..ac7bbdb7 --- /dev/null +++ b/src/amount.cc @@ -0,0 +1,2041 @@ +/** + * @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 the various + * flavors of commodity_t. + */ + +/* + * Copyright (c) 2003-2007, 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 "amount.h" +#include "binary.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; +bool amount_t::full_strings = 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) { + TRACE_CTOR(bigint_t, ""); + mpz_init(val); + } + bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, "mpz_t"); + mpz_init_set(val, _val); + } + bigint_t(const bigint_t& other) + : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC), + ref(1), index(0) { + TRACE_CTOR(bigint_t, "copy"); + mpz_init_set(val, other.val); + } + ~bigint_t(); +}; + +unsigned int sizeof_bigint_t() { + return sizeof(amount_t::bigint_t); +} + +#define MPZ(x) ((x)->val) + +#ifndef THREADSAFE +static mpz_t temp; // these are the global temp variables +static mpz_t divisor; +#endif + +static amount_t::bigint_t * true_value = NULL; + +inline amount_t::bigint_t::~bigint_t() { + TRACE_DTOR(bigint_t); + assert(ref == 0 || (! do_cleanup && this == true_value)); + mpz_clear(val); +} + +#ifndef THREADSAFE +base_commodities_map commodity_base_t::commodities; + +commodity_base_t::updater_t * commodity_base_t::updater = NULL; + +commodities_map commodity_t::commodities; +commodities_array * commodity_t::commodities_by_ident; +bool commodity_t::commodities_sorted = false; +commodity_t * commodity_t::null_commodity; +commodity_t * commodity_t::default_commodity = NULL; +#endif + +void amount_t::initialize() +{ + mpz_init(temp); + mpz_init(divisor); + + true_value = new amount_t::bigint_t; + mpz_set_ui(true_value->val, 1); + + commodity_base_t::updater = NULL; + + commodity_t::commodities_by_ident = new commodities_array; + + commodity_t::default_commodity = NULL; + commodity_t::null_commodity = commodity_t::create(""); + 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_t::create("s"); + commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); + + parse_conversion("1.0m", "60s"); + parse_conversion("1.0h", "60m"); +} + +void amount_t::shutdown() +{ + mpz_clear(temp); + mpz_clear(divisor); + + if (commodity_base_t::updater) { + delete commodity_base_t::updater; + commodity_base_t::updater = NULL; + } + + for (base_commodities_map::iterator i = commodity_base_t::commodities.begin(); + i != commodity_base_t::commodities.end(); + i++) + delete (*i).second; + + for (commodities_map::iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) + delete (*i).second; + + commodity_base_t::commodities.clear(); + commodity_t::commodities.clear(); + + delete commodity_t::commodities_by_ident; + commodity_t::commodities_by_ident = NULL; + + commodity_t::null_commodity = NULL; + commodity_t::default_commodity = NULL; + + true_value->ref--; + assert(true_value->ref == 0); + delete true_value; + true_value = NULL; +} + +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 long val) +{ + TRACE_CTOR(amount_t, "const long"); + if (val != 0) { + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), val); + } else { + quantity = NULL; + } + commodity_ = NULL; +} + +amount_t::amount_t(const unsigned long val) +{ + TRACE_CTOR(amount_t, "const unsigned long"); + if (val != 0) { + quantity = new bigint_t; + mpz_set_ui(MPZ(quantity), val); + } else { + quantity = NULL; + } + commodity_ = NULL; +} + +namespace { + unsigned char convert_double(mpz_t dest, double val) + { +#ifndef HAVE_GDTOA + // This code is far too imprecise to be worthwhile. + + mpf_t temp; + mpf_init_set_d(temp, val); + + mp_exp_t exp; + char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp); + + int len = std::strlen(buf); + if (len > 0 && buf[0] == '-') + exp++; + + if (exp <= len) { + exp = len - exp; + } else { + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = exp - len; + + char * newbuf = (char *)std::malloc(len + zeroes); + std::strcpy(newbuf, buf); + + int i; + for (i = 0; i < zeroes; i++) + newbuf[len + i] = '0'; + newbuf[len + i] = '\0'; + + free(buf); + buf = newbuf; + + exp = (len - exp) + zeroes; + } + + mpz_set_str(dest, buf, 10); + free(buf); + + return (unsigned char)exp; +#else + 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]; + + 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) + 1]; + newbuf[0] = '-'; + std::strcpy(&newbuf[1], result ? result : buf); + mpz_set_str(dest, newbuf, 10); + delete[] newbuf; + } else { + mpz_set_str(dest, result ? result : buf, 10); + } + + if (result) + delete[] result; + freedtoa(buf); + + return decpt; +#endif + } +} + +amount_t::amount_t(const double val) +{ + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + quantity->prec = convert_double(MPZ(quantity), val); + commodity_ = NULL; +} + +void amount_t::_release() +{ + DEBUG_("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_("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } + } + commodity_ = amt.commodity_; +} + +amount_t& amount_t::operator=(const string& val) +{ + std::istringstream str(val); + parse(str); + return *this; +} + +amount_t& amount_t::operator=(const char * val) +{ + string valstr(val); + 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 long val) +{ + if (val == 0) { + if (quantity) + _clear(); + } else { + commodity_ = NULL; + _init(); + mpz_set_si(MPZ(quantity), val); + } + return *this; +} + +amount_t& amount_t::operator=(const unsigned long val) +{ + if (val == 0) { + if (quantity) + _clear(); + } else { + commodity_ = NULL; + _init(); + mpz_set_ui(MPZ(quantity), val); + } + return *this; +} + +amount_t& amount_t::operator=(const double val) +{ + commodity_ = NULL; + _init(); + quantity->prec = convert_double(MPZ(quantity), val); + 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; +} + + +void amount_t::_clear() +{ + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; + } else { + assert(! commodity_); + } +} + + +amount_t& amount_t::operator+=(const amount_t& amt) +{ + if (commodity() != amt.commodity()) { + throw amount_exception + (string("Adding amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + } + + if (! amt.quantity) + return *this; + + if (! quantity) { + _copy(amt); + return *this; + } + + _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) +{ + if (commodity() != amt.commodity()) + throw amount_exception + (string("Subtracting amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + + 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 (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; +} + +amount_t& amount_t::operator*=(const amount_t& amt) +{ + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) { + throw amount_exception + (string("Multiplying amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + } + + if (! amt.quantity) { + *this = *this - *this; // preserve our commodity + goto finish; + } + else if (! quantity) { + *this = amt; + *this = *this - *this; // preserve the foreign commodity + goto finish; + } + + _dup(); + + mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + quantity->prec += amt.quantity->prec; + + finish: + if (! has_commodity()) + commodity_ = amt.commodity_; + + if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_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 (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) { + throw amount_exception + (string("Dividing amounts with different commodities: ") + + (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " + + (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"), + context()); + } + + if (! amt.quantity || ! amt) { + throw amount_exception("Divide by zero", context()); + } + else if (! quantity) { + *this = amt; + *this = *this - *this; // preserve the foreign commodity + goto finish; + } + + _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; + + finish: + 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->flags & BIGINT_KEEP_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; +} + +// unary negation +void amount_t::in_place_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 (has_commodity() && amt.commodity() && commodity() != amt.commodity()) + throw amount_exception + (string("Cannot compare amounts with different commodities: ") + + commodity().symbol() + " and " + amt.commodity().symbol(), + context()); + + 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)); + } +} + +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; +} + +bool amount_t::zero() const +{ + if (! quantity) + return true; + + if (has_commodity()) { + if (quantity->prec <= commodity().precision()) + return realzero(); + else + return round(commodity().precision()).sign() == 0; + } + return realzero(); +} + +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()); +} + +amount_t amount_t::value(const moment_t& moment) const +{ + if (quantity) { + amount_t amt(commodity().value(moment)); + if (! amt.realzero()) + return (amt * number()).round(); + } + return *this; +} + +amount_t amount_t::round(unsigned int prec) const +{ + amount_t t = *this; + + if (! quantity || quantity->prec <= prec) { + if (quantity && quantity->flags & BIGINT_KEEP_PREC) { + t._dup(); + t.quantity->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->flags &= ~BIGINT_KEEP_PREC; + + return t; +} + +amount_t amount_t::unround() const +{ + if (! quantity) { + amount_t t(0L); + assert(t.quantity); + t.quantity->flags |= BIGINT_KEEP_PREC; + return t; + } + else if (quantity->flags & BIGINT_KEEP_PREC) { + return *this; + } + + amount_t t = *this; + t._dup(); + t.quantity->flags |= BIGINT_KEEP_PREC; + + return t; +} + +void amount_t::print_quantity(std::ostream& out) const +{ + if (! quantity) { + out << "0"; + return; + } + + 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) { + out << "0"; + return; + } + + 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); +} + +void amount_t::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const +{ + amount_t base(*this); + if (! amount_t::keep_base && commodity().larger()) { + amount_t last(*this); + while (last.commodity().larger()) { + last /= last.commodity().larger()->number(); + last.commodity_ = last.commodity().larger()->commodity_; + if (last.abs() < 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 = 0; + + if (quantity) { + if (! comm || full_precision || 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 (! omit_commodity && ! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { + comm.write(out); + + if (comm.flags() & COMMODITY_STYLE_SEPARATED) + out << " "; + } + + if (negative) + out << "-"; + + if (! quantity || 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 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::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 (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()) { + out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } + + if (! omit_commodity && 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 (! omit_commodity && comm.annotated) { + annotated_commodity_t& ann(static_cast(comm)); + assert(&ann.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(); + + return; +} + +static 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; +} + +// 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, +}; + +static void parse_commodity(std::istream& in, 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 amount_exception("Quoted commodity symbol lacks closing quote", + context()); + } else { + READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); + } + symbol = buf; +} + +bool parse_annotations(std::istream& in, amount_t& price, + moment_t& date, string& tag) +{ + bool has_date = false; + + do { + char buf[256]; + char c = peek_next_nonws(in); + if (c == '{') { + if (price) + throw amount_exception("Commodity specifies more than one price", + context()); + + in.get(c); + READ_INTO(in, buf, 255, c, c != '}'); + if (c == '}') + in.get(c); + else + throw amount_exception("Commodity price lacks closing brace", context()); + + price.parse(buf, AMOUNT_PARSE_NO_MIGRATE); + price.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 (price.has_commodity() && + price.quantity->prec < price.commodity().precision()) + price = price.round(); // no need to retain individual precision + } + else if (c == '[') { + if (is_valid_moment(date)) + throw amount_exception("Commodity specifies more than one date", + context()); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ']'); + if (c == ']') + in.get(c); + else + throw amount_exception("Commodity date lacks closing bracket", + context()); + + date = parse_datetime(buf); + has_date = true; + } + else if (c == '(') { + if (! tag.empty()) + throw amount_exception("Commodity specifies more than one tag", + context()); + + in.get(c); + READ_INTO(in, buf, 255, c, c != ')'); + if (c == ')') + in.get(c); + else + throw amount_exception("Commodity tag lacks closing parenthesis", + context()); + + tag = buf; + } + else { + break; + } + } while (true); + + DEBUG_("amounts.commodities", + "Parsed commodity annotations: " + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + return has_date; +} + +void amount_t::parse(std::istream& in, unsigned char flags) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] + + string symbol; + string quant; + amount_t tprice; + moment_t tdate; + bool had_date = false; + 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')) + had_date = parse_annotations(in, tprice, tdate, 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')) + had_date = parse_annotations(in, tprice, tdate, tag); + } + } + + if (quant.empty()) + throw amount_exception("No quantity specified for amount", + context()); + + _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_ = NULL; + } else { + commodity_ = commodity_t::find(symbol); + if (! commodity_) { + commodity_ = commodity_t::create(symbol); + newly_created = true; + } + assert(commodity_); + + if (! tprice.realzero() || had_date || ! tag.empty()) + commodity_ = + annotated_commodity_t::find_or_create(*commodity_, tprice, tdate, tag); + } + + // 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().flags() & COMMODITY_STYLE_EUROPEAN) { + quantity->prec = quant.length() - last_comma - 1; + } + else if (last_period != 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 (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { + commodity().add_flags(comm_flags); + if (quantity->prec > commodity().precision()) + commodity().set_precision(quantity->prec); + } + + if (flags & AMOUNT_PARSE_NO_MIGRATE) + quantity->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); + 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(); +} + +void amount_t::in_place_reduce() +{ + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; + } +} + +void parse_conversion(const string& larger_str, + const 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.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::read(std::istream& in) +{ + commodity_t::ident_t ident; + read_binary_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = commodity_t::null_commodity; + else + commodity_ = (*commodity_t::commodities_by_ident)[ident - 1]; + + read_quantity(in); +} + +void amount_t::read(char *& data) +{ + commodity_t::ident_t ident; + read_binary_long(data, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = commodity_t::null_commodity; + else + commodity_ = (*commodity_t::commodities_by_ident)[ident - 1]; + + read_quantity(data); +} + +void amount_t::write(std::ostream& out) const +{ + if (commodity_) + write_binary_long(out, commodity_->ident); + else + write_binary_long(out, 0xffffffff); + + write_quantity(out); +} + + +#ifndef THREADSAFE +static char * bigints; +static char * bigints_next; +static unsigned int bigints_index; +static unsigned int bigints_count; +#endif + +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_("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } +} + +#ifndef THREADSAFE +static char buf[4096]; +#endif + +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_("ledger.validate", "amount_t: quantity->ref == 0"); + return false; + } + } + else if (commodity_) { + DEBUG_("ledger.validate", "amount_t: commodity_ != NULL"); + return false; + } + return true; +} + +void amount_t::annotate_commodity(const amount_t& tprice, + const moment_t& tdate, + const string& tag) +{ + const commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; + + if (commodity().annotated) { + this_ann = &static_cast(commodity()); + this_base = this_ann->ptr; + } else { + this_base = &commodity(); + } + assert(this_base); + + DEBUG_("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl + << " price " << tprice << " " + << " date " << tdate << " " + << " tag " << tag); + + commodity_t * ann_comm = + annotated_commodity_t::find_or_create + (*this_base, ! tprice && this_ann ? this_ann->price : tprice, + ! is_valid_moment(tdate) && this_ann ? this_ann->date : tdate, + tag.empty() && this_ann ? this_ann->tag : tag); + if (ann_comm) + set_commodity(*ann_comm); + + DEBUG_("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_("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(commodity())); + assert(ann_comm.base); + + commodity_t * new_comm; + + if ((_keep_price && ann_comm.price) || + (_keep_date && is_valid_moment(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 : moment_t(), + _keep_tag ? ann_comm.tag : ""); + } else { + new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); + } + assert(new_comm); + + amount_t t(*this); + t.set_commodity(*new_comm); + DEBUG_("amounts.commodities", " Reduced amount is " << t); + + return t; +} + +amount_t amount_t::price() const +{ + if (commodity_ && commodity_->annotated) { + amount_t t(((annotated_commodity_t *)commodity_)->price); + t *= number(); + DEBUG_("amounts.commodities", + "Returning price of " << *this << " = " << t); + return t; + } + return *this; +} + +moment_t amount_t::date() const +{ + if (commodity_ && commodity_->annotated) { + DEBUG_("amounts.commodities", + "Returning date of " << *this << " = " + << ((annotated_commodity_t *)commodity_)->date); + return ((annotated_commodity_t *)commodity_)->date; + } + return moment_t(); +} + + +void commodity_base_t::add_price(const moment_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 result + = history->prices.insert(history_pair(date, price)); + assert(result.second); + } +} + +bool commodity_base_t::remove_price(const moment_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 string& symbol) +{ + commodity_base_t * commodity = new commodity_base_t(symbol); + + DEBUG_("amounts.commodities", "Creating base commodity " << symbol); + + std::pair result + = commodities.insert(base_commodities_pair(symbol, commodity)); + assert(result.second); + + return commodity; +} + +bool commodity_t::needs_quotes(const 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_("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; +} + +commodity_t * commodity_t::create(const string& symbol) +{ + std::auto_ptr 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_("amounts.commodities", + "Creating commodity " << commodity->qualified_symbol); + + std::pair result + = commodities.insert(commodities_pair(symbol, commodity.get())); + if (! result.second) + return NULL; + + commodity->ident = commodities_by_ident->size(); + commodities_by_ident->push_back(commodity.get()); + + // 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 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_t::find(const string& symbol) +{ + DEBUG_("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 moment_t& moment) +{ + moment_t age; + amount_t price; + + if (history) { + assert(history->prices.size() > 0); + + if (! is_valid_moment(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 = moment_t(); + } + } else { + price = (*i).second; + } + } + } + } + + if (updater && ! (flags & COMMODITY_STYLE_NOMARKET)) + (*updater)(*this, moment, age, + (history && history->prices.size() > 0 ? + (*history->prices.rbegin()).first : moment_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(comm).price)) + return false; + + if (is_valid_moment(date) && + (! comm.annotated || + date != static_cast(comm).date)) + return false; + + if (! tag.empty() && + (! comm.annotated || + tag != static_cast(comm).tag)) + return false; + + return true; +} + +void +annotated_commodity_t::write_annotations(std::ostream& out, + const amount_t& price, + const moment_t& date, + const string& tag) +{ + if (price) + out << " {" << price << '}'; + + if (is_valid_moment(date)) + out << " [" << date << ']'; + + if (! tag.empty()) + out << " (" << tag << ')'; +} + +commodity_t * +annotated_commodity_t::create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag, + const string& mapping_key) +{ + std::auto_ptr 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_("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 result + = commodities.insert(commodities_pair(mapping_key, commodity.get())); + if (! result.second) + return NULL; + + commodity->ident = commodities_by_ident->size(); + commodities_by_ident->push_back(commodity.get()); + + return commodity.release(); +} + +namespace { + string make_qualified_name(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag) + { + if (price < 0) + throw amount_exception("A commodity's price may not be negative", + context()); + + std::ostringstream name; + + comm.write(name); + annotated_commodity_t::write_annotations(name, price, date, tag); + + DEBUG_("amounts.commodities", "make_qualified_name for " + << comm.qualified_symbol << std::endl + << " price " << price << " " + << " date " << date << " " + << " tag " << tag); + + DEBUG_("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 moment_t& date, + const string& tag) +{ + 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(leftcomm)); + annotated_commodity_t& arightcomm(static_cast(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.in_place_reduce(); + amount_t rightprice(arightcomm.price); + rightprice.in_place_reduce(); + + if (leftprice.commodity() == rightprice.commodity()) { + amount_t val = leftprice - rightprice; + if (val) + return val < 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(); + + amount_t val = leftprice - rightprice; + if (val) + return val < 0; + } + } + + if (! is_valid_moment(aleftcomm.date) && + is_valid_moment(arightcomm.date)) + return true; + if (is_valid_moment(aleftcomm.date) && + ! is_valid_moment(arightcomm.date)) + return false; + + if (is_valid_moment(aleftcomm.date) && + is_valid_moment(arightcomm.date)) { + duration_t diff = aleftcomm.date - arightcomm.date; + return diff.is_negative(); + } + + 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; + + assert(0); + return true; + } +} + +} // namespace ledger diff --git a/src/amount.h b/src/amount.h new file mode 100644 index 00000000..dcd30b8d --- /dev/null +++ b/src/amount.h @@ -0,0 +1,760 @@ +/** + * @file amount.h + * @author John Wiegley + * @date Wed Apr 18 22:05:53 2007 + * + * @brief Types for handling commoditized math. + * + * This file contains two of the most basic types in Ledger: amount_t + * commodity_t, and annotated_commodity_t. Both the commodity types + * share a common base class, commodity_base_t. These four class + * together allow Ledger to handle mathematical expressions involving + * differing commodities, or in some cases math using no commodities + * at all (such as increasing a dollar amount by a multiplier). + */ + +/* + * Copyright (c) 2003-2007, 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 _AMOUNT_H +#define _AMOUNT_H + +#include "utils.h" +#include "times.h" + +namespace ledger { + +extern bool do_cleanup; + +class commodity_t; + +/** + * @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. + * Internally, precision is always kept to an excessive degree. + */ + +class amount_t +{ + public: + class bigint_t; + + static void initialize(); + static void shutdown(); + + static bool keep_price; + static bool keep_date; + static bool keep_tag; + static bool keep_base; + static bool full_strings; + + protected: + void _init(); + void _copy(const amount_t& amt); + void _release(); + void _dup(); + void _resize(unsigned int prec); + void _clear(); + + bigint_t * quantity; + commodity_t * commodity_; + + public: + // constructors + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR(amount_t, ""); + } + amount_t(const amount_t& amt) : quantity(NULL) { + TRACE_CTOR(amount_t, "copy"); + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + } + amount_t(const string& val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const string&"); + parse(val); + } + amount_t(const char * val) : quantity(NULL) { + TRACE_CTOR(amount_t, "const char *"); + parse(val); + } + amount_t(const long val); + amount_t(const unsigned long val); + amount_t(const double val); + + // destructor + ~amount_t() { + TRACE_DTOR(amount_t); + if (quantity) + _release(); + } + + amount_t number() const { + amount_t temp(*this); + temp.clear_commodity(); + return temp; + } + + bool has_commodity() const; + commodity_t& commodity() const; + void set_commodity(commodity_t& comm) { + commodity_ = &comm; + } + void annotate_commodity(const amount_t& price, + const moment_t& date = moment_t(), + const 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; + moment_t date() const; + + bool null() const { + return ! quantity && ! has_commodity(); + } + + // assignment operator + amount_t& operator=(const amount_t& amt); + amount_t& operator=(const string& val); + amount_t& operator=(const char * val); + amount_t& operator=(const long val); + amount_t& operator=(const unsigned long val); + amount_t& operator=(const double val); + + // 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 + amount_t& operator+=(T val) { + return *this += amount_t(val); + } + template + amount_t& operator-=(T val) { + return *this -= amount_t(val); + } + template + amount_t& operator*=(T val) { + return *this *= amount_t(val); + } + template + amount_t& operator/=(T val) { + return *this /= amount_t(val); + } + + // 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 + amount_t operator+(T val) const { + amount_t temp = *this; + temp += val; + return temp; + } + template + amount_t operator-(T val) const { + amount_t temp = *this; + temp -= val; + return temp; + } + template + amount_t operator*(T val) const { + amount_t temp = *this; + temp *= val; + return temp; + } + template + amount_t operator/(T val) const { + amount_t temp = *this; + temp /= val; + return temp; + } + + // unary negation + void in_place_negate(); + amount_t negate() const { + amount_t temp = *this; + temp.in_place_negate(); + return temp; + } + amount_t operator-() const { + return negate(); + } + + // test for zero and non-zero + int sign() const; + bool zero() const; + bool realzero() const { + return sign() == 0; + } + operator bool() const { + return ! zero(); + } + operator string() const { + return to_string(); + } + + operator long() const; + operator double() const; + + string to_string() const; + string to_fullstring() const; + string quantity_string() 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 + void parse_num(T num) { + std::ostringstream temp; + temp << num; + std::istringstream in(temp.str()); + parse(in); + } + + // POD comparisons +#define AMOUNT_CMP_INT(OP) \ + template \ + 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 + bool operator!=(T num) const { + return ! (*this == num); + } + + amount_t value(const moment_t& moment) const; + + amount_t abs() const { + if (*this < 0) + return negate(); + return *this; + } + + void in_place_reduce(); + amount_t reduce() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + bool valid() const; + + static amount_t exact(const string& value); + + // 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 bool parse_annotations(std::istream& in, amount_t& price, + moment_t& date, string& tag); + + // Streaming interface + + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + +#define AMOUNT_PARSE_NO_MIGRATE 0x01 +#define AMOUNT_PARSE_NO_REDUCE 0x02 + + void print(std::ostream& out, bool omit_commodity = false, + bool full_precision = false) const; + void parse(std::istream& in, unsigned char flags = 0); + void parse(const string& str, unsigned char flags = 0) { + std::istringstream stream(str); + parse(stream, flags); + } + + void print_quantity(std::ostream& out) const; + + void write(std::ostream& out) const; + void read(std::istream& in); + void read(char *& data); + + void write_quantity(std::ostream& out) const; + void read_quantity(std::istream& in); + void read_quantity(char *& data); +}; + +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(); +} + +#define DEFINE_AMOUNT_OPERATORS(T) \ +inline amount_t operator+(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp += amt; \ + return temp; \ +} \ +inline amount_t operator-(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp -= amt; \ + return temp; \ +} \ +inline amount_t operator*(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp *= amt; \ + return temp; \ +} \ +inline amount_t operator/(const T val, const amount_t& amt) { \ + amount_t temp(val); \ + temp /= amt; \ + return temp; \ +} \ + \ +inline bool operator<(const T val, const amount_t& amt) { \ + return amount_t(val) < amt; \ +} \ +inline bool operator<=(const T val, const amount_t& amt) { \ + return amount_t(val) <= amt; \ +} \ +inline bool operator>(const T val, const amount_t& amt) { \ + return amount_t(val) > amt; \ +} \ +inline bool operator>=(const T val, const amount_t& amt) { \ + return amount_t(val) >= amt; \ +} \ +inline bool operator==(const T val, const amount_t& amt) { \ + return amount_t(val) == amt; \ +} \ +inline bool operator!=(const T val, const amount_t& amt) { \ + return amount_t(val) != amt; \ +} + +DEFINE_AMOUNT_OPERATORS(long) +DEFINE_AMOUNT_OPERATORS(unsigned long) +DEFINE_AMOUNT_OPERATORS(double) + +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + amt.print(out, false, amount_t::full_strings); + return out; +} +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 history_map; +typedef std::pair history_pair; + +class commodity_base_t; + +typedef std::map base_commodities_map; +typedef std::pair 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; + string name; + 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) { + TRACE_CTOR(commodity_base_t, ""); + } + + commodity_base_t(const commodity_base_t&) { + TRACE_CTOR(commodity_base_t, "copy"); + assert(0); + } + + commodity_base_t(const string& _symbol, + unsigned int _precision = 0, + unsigned int _flags = COMMODITY_STYLE_DEFAULTS) + : precision(_precision), flags(_flags), + smaller(NULL), larger(NULL), symbol(_symbol), history(NULL) { + TRACE_CTOR(commodity_base_t, "const string&, unsigned int, unsigned int"); + } + + ~commodity_base_t() { + TRACE_DTOR(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 string& symbol); + + string symbol; + + struct history_t { + history_map prices; + ptime last_lookup; + history_t() : last_lookup() {} + }; + history_t * history; + + void add_price(const moment_t& date, const amount_t& price); + bool remove_price(const moment_t& date); + amount_t value(const moment_t& moment = now); + + class updater_t { + public: + virtual ~updater_t() {} + virtual void operator()(commodity_base_t& commodity, + const moment_t& moment, + const moment_t& date, + const moment_t& last, + amount_t& price) = 0; + }; + friend class updater_t; + + static updater_t * updater; +}; + +typedef std::map commodities_map; +typedef std::pair commodities_pair; + +typedef std::vector commodities_array; + +class commodity_t +{ + friend class annotated_commodity_t; + + public: + // This map remembers all commodities that have been defined. + + static commodities_map commodities; + static commodities_array * commodities_by_ident; + static bool commodities_sorted; + static commodity_t * null_commodity; + static commodity_t * default_commodity; + + static commodity_t * create(const string& symbol); + static commodity_t * find(const string& name); + static commodity_t * find_or_create(const string& symbol); + + static bool needs_quotes(const string& symbol); + + static void make_alias(const string& symbol, + commodity_t * commodity); + + // These are specific to each commodity reference + + typedef unsigned long ident_t; + + ident_t ident; + commodity_base_t * base; + string qualified_symbol; + bool annotated; + + public: + explicit commodity_t() : base(NULL), annotated(false) { + TRACE_CTOR(commodity_t, ""); + } + commodity_t(const commodity_t& o) + : ident(o.ident), base(o.base), + qualified_symbol(o.qualified_symbol), annotated(o.annotated) { + TRACE_CTOR(commodity_t, "copy"); + } + virtual ~commodity_t() { + TRACE_DTOR(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); + } + + string base_symbol() const { + return base->symbol; + } + string symbol() const { + return qualified_symbol; + } + + void write(std::ostream& out) const { + out << symbol(); + } + + string name() const { + return base->name; + } + void set_name(const string& arg) { + base->name = arg; + } + + string note() const { + return base->note; + } + void set_note(const 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 moment_t& date, const amount_t& price) { + return base->add_price(date, price); + } + bool remove_price(const moment_t& date) { + return base->remove_price(date); + } + amount_t value(const moment_t& moment = now) const { + return base->value(moment); + } + + bool valid() const; +}; + +class annotated_commodity_t : public commodity_t +{ + public: + const commodity_t * ptr; + + amount_t price; + moment_t date; + string tag; + + explicit annotated_commodity_t() { + TRACE_CTOR(annotated_commodity_t, ""); + annotated = true; + } + virtual ~annotated_commodity_t() { + TRACE_DTOR(annotated_commodity_t); + } + + 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 moment_t& date, + const string& tag); + + private: + static commodity_t * create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const string& tag, + const string& mapping_key); + + static commodity_t * find_or_create(const commodity_t& comm, + const amount_t& price, + const moment_t& date, + const 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 bool amount_t::has_commodity() const { + return commodity_ && commodity_ != commodity_t::null_commodity; +} + +inline commodity_t& amount_t::commodity() const { + if (! commodity_) + return *commodity_t::null_commodity; + else + return *commodity_; +} + +void parse_conversion(const string& larger_str, + const string& smaller_str); + +DECLARE_EXCEPTION(amount_exception); + +struct compare_amount_commodities { + bool operator()(const amount_t * left, const amount_t * right) const; +}; + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/src/balance.cc b/src/balance.cc new file mode 100644 index 00000000..487f749f --- /dev/null +++ b/src/balance.cc @@ -0,0 +1,313 @@ +#include "balance.h" + +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); + + throw_(amount_exception, + "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 amount_t(); +} + +balance_t balance_t::value(const moment_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; +} + +moment_t balance_t::date() const +{ + moment_t temp; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) { + moment_t tdate = (*i).second.date(); + if (! is_valid_moment(temp) && is_valid_moment(tdate)) + temp = tdate; + else if (temp != tdate) + return moment_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::vector amounts_array; + amounts_array 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_array::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 amount_exception(errmsg.str(), context()); + } +} + +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 amount_exception(errmsg.str(), context()); + } + } + 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 amount_exception(errmsg.str(), context()); + } + 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 amount_exception(errmsg.str(), context()); + } +} + +balance_t& balance_t::operator/=(const amount_t& amt) +{ + if (amt.realzero()) { + std::ostringstream errmsg; + errmsg << "Attempt to divide by zero: " << *this << " / " << amt; + throw amount_exception(errmsg.str(), context()); + } + 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 amount_exception(errmsg.str(), context()); + } + } + 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; + + throw_(amount_exception, + "Cannot convert a balance with " << + "multiple commodities to an amount: " << temp); + } +} + +} // namespace ledger diff --git a/src/balance.h b/src/balance.h new file mode 100644 index 00000000..2a6f3072 --- /dev/null +++ b/src/balance.h @@ -0,0 +1,969 @@ +#ifndef _BALANCE_H +#define _BALANCE_H + +#include "amount.h" + +namespace ledger { + +typedef std::map amounts_map; +typedef std::pair 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() { + TRACE_CTOR(balance_t, ""); + } + balance_t(const balance_t& bal) { + TRACE_CTOR(balance_t, "copy"); + for (amounts_map::const_iterator i = bal.amounts.begin(); + i != bal.amounts.end(); + i++) + *this += (*i).second; + } + balance_t(const amount_t& amt) { + TRACE_CTOR(balance_t, "const amount_t&"); + if (! amt.realzero()) + amounts.insert(amounts_pair(&amt.commodity(), amt)); + } + template + balance_t(T val) { + TRACE_CTOR(balance_t, "T"); + amount_t amt(val); + if (! amt.realzero()) + amounts.insert(amounts_pair(&amt.commodity(), amt)); + } + + ~balance_t() { + TRACE_DTOR(balance_t); + } + + // 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 + balance_t& operator=(T val) { + amounts.clear(); + *this += val; + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + bool operator!=(T val) const { + return ! (*this == val); + } + + // unary negation + void in_place_negate() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second = (*i).second.negate(); + } + balance_t negate() const { + balance_t temp = *this; + temp.in_place_negate(); + return temp; + } + balance_t operator-() const { + return negate(); + } + + // 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 moment_t& moment = now) const; + balance_t price() const; + moment_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 in_place_abs() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second = (*i).second.abs(); + } + balance_t abs() const { + balance_t temp = *this; + temp.in_place_abs(); + return temp; + } + + void in_place_reduce() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second.in_place_reduce(); + } + balance_t reduce() const { + balance_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + void in_place_round() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); + i++) + (*i).second = (*i).second.round(); + } + balance_t round() const { + balance_t temp(*this); + temp.in_place_round(); + return temp; + } + + 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 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) { + TRACE_CTOR(balance_pair_t, ""); + } + balance_pair_t(const balance_pair_t& bal_pair) + : quantity(bal_pair.quantity), cost(NULL) { + TRACE_CTOR(balance_pair_t, "copy"); + if (bal_pair.cost) + cost = new balance_t(*bal_pair.cost); + } + balance_pair_t(const balance_t& _quantity) + : quantity(_quantity), cost(NULL) { + TRACE_CTOR(balance_pair_t, "const balance_t&"); + } + balance_pair_t(const amount_t& _quantity) + : quantity(_quantity), cost(NULL) { + TRACE_CTOR(balance_pair_t, "const amount_t&"); + } + template + balance_pair_t(T val) : quantity(val), cost(NULL) { + TRACE_CTOR(balance_pair_t, "T"); + } + + // destructor + ~balance_pair_t() { + TRACE_DTOR(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 + balance_pair_t& operator=(T val) { + if (cost) { + delete cost; + cost = NULL; + } + quantity = val; + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + bool operator!=(T val) const { + return ! (*this == val); + } + + // unary negation + void in_place_negate() { + quantity = quantity.negate(); + if (cost) + *cost = cost->negate(); + } + balance_pair_t negate() const { + balance_pair_t temp = *this; + temp.in_place_negate(); + return temp; + } + balance_pair_t operator-() const { + return negate(); + } + + // 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 in_place_abs() { + quantity = quantity.abs(); + if (cost) + *cost = cost->abs(); + } + balance_pair_t abs() const { + balance_pair_t temp = *this; + temp.in_place_abs(); + return temp; + } + + amount_t amount(const commodity_t& commodity = + *commodity_t::null_commodity) const { + return quantity.amount(commodity); + } + balance_t value(const moment_t& moment = now) const { + return quantity.value(moment); + } + balance_t price() const { + return quantity.price(); + } + moment_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& amt, + const amount_t * a_cost = NULL) { + if (a_cost && ! cost) + cost = new balance_t(quantity); + quantity += amt; + if (cost) + *cost += a_cost ? *a_cost : amt; + return *this; + } + + bool valid() { + return quantity.valid() && (! cost || cost->valid()); + } + + void in_place_reduce() { + quantity.in_place_reduce(); + if (cost) cost->in_place_reduce(); + } + balance_pair_t reduce() const { + balance_pair_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + void in_place_round() { + quantity = quantity.round(); + if (cost) + *cost = cost->round(); + } + balance_pair_t round() const { + balance_pair_t temp(*this); + temp.in_place_round(); + return temp; + } + + balance_pair_t unround() { + balance_pair_t temp(quantity.unround()); + if (cost) + temp.cost = new balance_t(cost->unround()); + 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/src/binary.cc b/src/binary.cc new file mode 100644 index 00000000..d3238b5a --- /dev/null +++ b/src/binary.cc @@ -0,0 +1,1013 @@ +#include "binary.h" + +namespace ledger { + +#if 0 +static unsigned long binary_magic_number = 0xFFEED765; +#if defined(DEBUG_ON) +static unsigned long format_version = 0x00030000; +#else +static unsigned long format_version = 0x00030000; +#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; +#endif + +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); +} + +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); +} + +void read_binary_string(std::istream& in, 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); +} + +void read_binary_string(char *& data, 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 = string(data, slen); + data += slen; + } + else if (len) { + str = string(data, len); + data += len; + } + else { + str = ""; + } + + read_binary_guard(data, 0x3002); +} + +void read_binary_string(char *& data, 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) string(data, slen); + data += slen; + } + else if (len) { + new(str) string(data, len); + data += len; + } + else { + new(str) string(""); + } + + read_binary_guard(data, 0x3002); +} + +#if 0 +inline void read_binary_value(char *& data, value_t& val) +{ + val.type = static_cast(read_binary_long(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, *((moment_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); + string pattern; + read_binary_string(data, pattern); + + mask = new mask_t(pattern); + mask->exclude = exclude; +} + +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(data) - 1]; + + unsigned char flag = read_binary_number(data); + if (flag == 0) { + read_binary_amount(data, xact->amount); + } + else if (flag == 1) { + string expr; + read_binary_string(data, expr); + xact->amount_expr = expr; + + repitem_t * item = + repitem_t::wrap(xact, static_cast(xact->entry->data)); + xact->data = item; + + xact->amount = valexpr_t(xact->amount_expr).calc(item).to_amount(); + } + + 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(data); + read_binary_long(data, xact->beg_line); + xact->end_pos = read_binary_long(data); + read_binary_long(data, xact->end_line); + + xact->data = NULL; +} + +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(data); + read_binary_long(data, entry->beg_line); + entry->end_pos = read_binary_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(data); + i < count; + i++) { + new(xact_pool) transaction_t; + xact_pool->entry = static_cast(entry); + 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) +{ + entry->data = + repitem_t::wrap(entry, static_cast(entry->journal->data)); + + 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); + + string pred_str; + read_binary_string(data, &pred_str); + entry->predicate.parse(pred_str); +} + +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(data); + i < count; + i++) { + moment_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(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(data) - 1]; + + read_binary_string(data, commodity->qualified_symbol); + commodity->annotated = true; + + commodity->ptr = + commodities[read_binary_long(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(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, + journal_t * journal, + account_t * master, + const string& original_file) +{ + 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 (! original_file.empty()) { + for (unsigned short i = 0, + count = read_binary_number(in); + i < count; + i++) { + 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(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(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(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(data); + unsigned long auto_count = read_binary_long(data); + unsigned long period_count = read_binary_long(data); + unsigned long xact_count = read_binary_number(data); + unsigned long bigint_count = read_binary_number(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(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 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(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(data); + commodities = commodities_next = new commodity_t *[c_count]; + + for (commodity_t::ident_t i = 0; i < c_count; i++) { + commodity_t * commodity; + 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 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(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; + entry_pool->journal = journal; + read_binary_entry(data, entry_pool, xact_pool, finalize); + 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; +} +#endif + +#if 0 +bool binary_parser_t::test(std::istream& in) const +{ + if (read_binary_number_nocheck(in) == binary_magic_number && + read_binary_number_nocheck(in) == format_version) + return true; + + in.clear(); + in.seekg(0, std::ios::beg); + return false; +} + +unsigned int binary_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ +#if 0 + return read_binary_journal(in, journal, master, + original_file ? *original_file : ""); +#endif +} +#endif + + +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); +} + +void write_binary_string(std::ostream& out, const string& str) +{ + write_binary_guard(out, 0x3001); + + unsigned long len = str.length(); + if (len > 255) { + assert(len < 65536); + write_binary_number_nocheck(out, 0xff); + write_binary_number_nocheck(out, len); + } else { + write_binary_number_nocheck(out, len); + } + + if (len) + out.write(str.c_str(), len); + + write_binary_guard(out, 0x3002); +} + +#if 0 +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, *((moment_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_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(out, 0); + write_binary_amount(out, amount_t()); + } + else if (! xact->amount_expr.empty()) { + write_binary_number(out, 1); + write_binary_string(out, xact->amount_expr); + } + else { + write_binary_number(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.empty()) { + 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_string(out, entry->predicate.expr); +} + +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(out, 0); + } else { + write_binary_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(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(out, 0xffffffff); + + write_binary_string(out, account->name); + write_binary_string(out, account->note); + write_binary_number(out, account->depth); + + write_binary_long(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(out, 0); + } else { + write_binary_number(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(out, 0); + + // Write out the accounts + + write_binary_long(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(out, journal->entries.size()); + write_binary_long(out, journal->auto_entries.size()); + write_binary_long(out, journal->period_entries.size()); + + ostream_pos_type xacts_val = out.tellp(); + write_binary_number(out, 0); + + ostream_pos_type bigints_val = out.tellp(); + write_binary_number(out, 0); + + bigints_count = 0; + + // Write out the commodities + + write_binary_long + (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 + (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(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(out, data_size); + out.seekp(xacts_val); + write_binary_number(out, xact_count); + out.seekp(bigints_val); + write_binary_number(out, bigints_count); +} +#endif + +} // namespace ledger diff --git a/src/binary.h b/src/binary.h new file mode 100644 index 00000000..528217fa --- /dev/null +++ b/src/binary.h @@ -0,0 +1,252 @@ +#ifndef _BINARY_H +#define _BINARY_H + +#include "parser.h" + +namespace ledger { + +#if 0 +class binary_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; +#endif + +template +inline void read_binary_number_nocheck(std::istream& in, T& num) { + in.read((char *)&num, sizeof(num)); +} + +template +inline void read_binary_number_nocheck(char *& data, T& num) { + num = *((T *) data); + data += sizeof(T); +} + +template +inline T read_binary_number_nocheck(std::istream& in) { + T num; + read_binary_number_nocheck(in, num); + return num; +} + +template +inline T read_binary_number_nocheck(char *& data) { + T num; + read_binary_number_nocheck(data, num); + return num; +} + +#if DEBUG_LEVEL >= ALPHA +#define read_binary_guard(in, id) \ + if (read_binary_number_nocheck(in) != id) \ + assert(0); +#else +#define read_binary_guard(in, id) +#endif + +template +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); +} + +template +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); +} + +template +inline T read_binary_number(std::istream& in) { + T num; + read_binary_number(in, num); + return num; +} + +template +inline T read_binary_number(char *& data) { + T num; + read_binary_number(data, num); + return num; +} + +void read_binary_bool(std::istream& in, bool& num); +void read_binary_bool(char *& data, bool& num); + +inline bool read_binary_bool(std::istream& in) { + bool num; + read_binary_bool(in, num); + return num; +} + +inline bool read_binary_bool(char *& data) { + bool num; + read_binary_bool(data, num); + return num; +} + +template +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 +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 +inline T read_binary_long(std::istream& in) { + T num; + read_binary_long(in, num); + return num; +} + +template +inline T read_binary_long(char *& data) { + T num; + read_binary_long(data, num); + return num; +} + +void read_binary_string(std::istream& in, string& str); +void read_binary_string(char *& data, string& str); +void read_binary_string(char *& data, string * str); + +inline string read_binary_string(std::istream& in) { + string temp; + read_binary_string(in, temp); + return temp; +} + +inline string read_binary_string(char *& data) { + string temp; + read_binary_string(data, temp); + return temp; +} + + +template +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(out, id) +#else +#define write_binary_guard(in, id) +#endif + +template +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); +} + +void write_binary_bool(std::ostream& out, bool num); + +template +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(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); +} + +void write_binary_string(std::ostream& out, const string& str); + + + +#if 0 +void write_binary_journal(std::ostream& out, journal_t * journal); +#endif + +} // namespace ledger + +#endif // _BINARY_H diff --git a/src/context.h b/src/context.h new file mode 100644 index 00000000..3851d073 --- /dev/null +++ b/src/context.h @@ -0,0 +1,28 @@ +#ifndef _CONTEXT_H +#define _CONTEXT_H + +namespace ledger { + +class context +{ +public: + string context; // ex: 'While parsing file "%R" at line %L' + + string resource; // ex: ledger.dat + long linenum_beg; // ex: 1010 + long linenum_end; // ex: 1010 + long colnum_beg; // ex: 8 + long colnum_end; // ex: 8 + long position_beg; + long position_end; + + string text; // ex: (The multi-line text of an entry) + long linenum_beg_off; // ex: 2 / -1 means start at beginning + long linenum_end_off; // ex: 2 / -1 means start at beginning + long colnum_beg_off; // ex: 8 / -1 means start + long colnum_end_off; // ex: 8 / -1 means start +}; + +} // namespace ledger + +#endif // _CONTEXT_H diff --git a/src/csv.cc b/src/csv.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/csv.h b/src/csv.h new file mode 100644 index 00000000..e69de29b diff --git a/src/derive.cc b/src/derive.cc new file mode 100644 index 00000000..6586c1f4 --- /dev/null +++ b/src/derive.cc @@ -0,0 +1,178 @@ +#include "derive.h" +#include "mask.h" + +namespace ledger { + +void derive_command::operator() + (value_t& result, xml::xpath_t::scope_t * locals) +{ +#if 0 + std::ostream& out = *get_ptr(locals, 0); + repitem_t * items = get_ptr(locals, 1); + strings_list& args = *get_ptr(locals, 2); + + std::auto_ptr added(new entry_t); + + entry_t * matching = NULL; + + strings_list::iterator i = args.begin(); + + added->_date = *i++; + if (i == args.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 == args.end() || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { + acct = journal.find_account("Expenses"); + } + else if (i != args.end()) { + acct = journal.find_account_re(*i); + if (! acct) + acct = journal.find_account(*i); + assert(acct); + i++; + } + + if (i == args.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 > 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()); + } + } + + if (journal.basket) + acct = journal.basket; + else + acct = journal.find_account("Equity"); + + added->add_transaction(new transaction_t(acct)); + } + else if (i == args.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 != args.end()) { + account_t * acct = journal.find_account_re(*i); + if (! acct) + acct = journal.find_account(*i); + assert(acct); + added->transactions.back()->account = acct; + } + } + else { + while (i != args.end()) { + 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: + if (! acct) + acct = journal.find_account_re(re_pat); + if (! acct) + acct = journal.find_account(re_pat); + + transaction_t * xact; + if (i == args.end()) { + if (amt) + xact = new transaction_t(acct, *amt); + else + xact = new transaction_t(acct); + } else { + xact = new transaction_t(acct, amount_t(*i++)); + if (! xact->amount.commodity()) { + if (amt) + xact->amount.set_commodity(amt->commodity()); + else if (commodity_t::default_commodity) + xact->amount.set_commodity(*commodity_t::default_commodity); + } + } + added->add_transaction(xact); + } + + assert(matching->transactions.back()->account); + if (account_t * draw_acct = matching->transactions.back()->account) + added->add_transaction(new transaction_t(draw_acct)); + } + + done: + if (! 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(); +#endif +} + +} // namespace ledger diff --git a/src/derive.h b/src/derive.h new file mode 100644 index 00000000..c0607fc2 --- /dev/null +++ b/src/derive.h @@ -0,0 +1,18 @@ +#ifndef _DERIVE_H +#define _DERIVE_H + +#include "journal.h" + +namespace ledger { + +class derive_command : public xml::xpath_t::functor_t +{ + public: + derive_command() : xml::xpath_t::functor_t("entry", true) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); +}; + +} // namespace ledger + +#endif // _DERIVE_H diff --git a/src/emacs.cc b/src/emacs.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/emacs.h b/src/emacs.h new file mode 100644 index 00000000..e69de29b diff --git a/src/error.h b/src/error.h new file mode 100644 index 00000000..5cbf54fb --- /dev/null +++ b/src/error.h @@ -0,0 +1,151 @@ +#ifndef _ERROR_H +#define _ERROR_H + +#import "context.h" + +namespace ledger { + +class exception : public std::exception +{ +protected: + string reason; + +public: + std::list context_stack; + + exception(const string& _reason, + const context& immediate_ctxt) throw() + : reason(_reason) { + EXCEPTION(reason); + push(immediate_ctxt); + } + + virtual ~exception() throw() {} + + void push(const context& intermediate_ctxt) throw() { + context_stack.push_front(intermediate_ctxt); + } + + void write(std::ostream& out) const throw() { +#if 0 + for (std::list::const_iterator + i = context_stack.begin(); + i != context_stack.end(); + i++) + (*i).write(out); +#endif + } + + const char * what() const throw() { + return reason.c_str(); + } +}; + +#define DECLARE_EXCEPTION(name) \ + class name : public exception { \ + public: \ + name(const string& _reason, \ + const context& immediate_ctxt) throw() \ + : exception(_reason, immediate_ctxt) {} \ + } + +#if 0 + +class error_context +{ + public: + string desc; + + error_context(const 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: + string file; + unsigned long line; + public: + file_context(const string& _file, unsigned long _line, + const 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: + string line; + long pos; + + line_context(const string& _line, long _pos, + const 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 error : public str_exception { + public: + error(const string& _reason, error_context * _ctxt = NULL) throw() + : str_exception(_reason, _ctxt) {} + virtual ~error() throw() {} +}; + +class fatal : public str_exception { + public: + fatal(const string& _reason, error_context * _ctxt = NULL) throw() + : str_exception(_reason, _ctxt) {} + virtual ~fatal() throw() {} +}; + +class fatal_assert : public fatal { + public: + fatal_assert(const string& _reason, error_context * _ctxt = NULL) throw() + : fatal(string("assertion failed '") + _reason + "'", _ctxt) {} + virtual ~fatal_assert() throw() {} +}; + +#endif // 0 + +inline void unexpected(char c, char wanted) +{ +#if 0 + if ((unsigned char) c == 0xff) { + if (wanted) + throw new error(string("Missing '") + wanted + "'"); + else + throw new error("Unexpected end of input"); + } else { + if (wanted) + throw new error(string("Invalid char '") + c + + "' (wanted '" + wanted + "')"); + else + throw new error(string("Invalid char '") + c + "'"); + } +#endif +} + +} // namespace ledger + +#endif // _ERROR_H diff --git a/src/fdstream.hpp b/src/fdstream.hpp new file mode 100644 index 00000000..a74a5781 --- /dev/null +++ b/src/fdstream.hpp @@ -0,0 +1,184 @@ +/* The following code declares classes to read from and write to + * file descriptore or file handles. + * + * See + * http://www.josuttis.com/cppcode + * for details and the latest version. + * + * - open: + * - integrating BUFSIZ on some systems? + * - optimized reading of multiple characters + * - stream for reading AND writing + * - i18n + * + * (C) Copyright Nicolai M. Josuttis 2001. + * Permission to copy, use, modify, sell and distribute this software + * is granted provided this copyright notice appears in all copies. + * This software is provided "as is" without express or implied + * warranty, and with no claim as to its suitability for any purpose. + * + * Version: Jul 28, 2002 + * History: + * Jul 28, 2002: bugfix memcpy() => memmove() + * fdinbuf::underflow(): cast for return statements + * Aug 05, 2001: first public version + */ +#ifndef BOOST_FDSTREAM_HPP +#define BOOST_FDSTREAM_HPP + +#include +#include +#include +// for EOF: +#include +// for memmove(): +#include + + +// low-level read and write functions +#ifdef _MSC_VER +# include +#else +# include +//extern "C" { +// int write (int fd, const char* buf, int num); +// int read (int fd, char* buf, int num); +//} +#endif + + +// BEGIN namespace BOOST +namespace boost { + + +/************************************************************ + * fdostream + * - a stream that writes on a file descriptor + ************************************************************/ + + +class fdoutbuf : public std::streambuf { + protected: + int fd; // file descriptor + public: + // constructor + fdoutbuf (int _fd) : fd(_fd) { + } + protected: + // write one character + virtual int_type overflow (int_type c) { + if (c != EOF) { + char z = c; + if (write (fd, &z, 1) != 1) { + return EOF; + } + } + return c; + } + // write multiple characters + virtual + std::streamsize xsputn (const char* s, + std::streamsize num) { + return write(fd,s,num); + } +}; + +class fdostream : public std::ostream { + protected: + fdoutbuf buf; + public: + fdostream (int fd) : std::ostream(0), buf(fd) { + rdbuf(&buf); + } +}; + + +/************************************************************ + * fdistream + * - a stream that reads on a file descriptor + ************************************************************/ + +class fdinbuf : public std::streambuf { + protected: + int fd; // file descriptor + 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() + */ + fdinbuf (int _fd) : fd(_fd) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + 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; + num = read (fd, buffer+pbSize, bufSize); + if (num <= 0) { + // ERROR or EOF + return EOF; + } + + // 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 fdistream : public std::istream { + protected: + fdinbuf buf; + public: + fdistream (int fd) : std::istream(0), buf(fd) { + rdbuf(&buf); + } +}; + + +} // END namespace boost + +#endif /*BOOST_FDSTREAM_HPP*/ diff --git a/src/format.cc b/src/format.cc new file mode 100644 index 00000000..774af6ca --- /dev/null +++ b/src/format.cc @@ -0,0 +1,235 @@ +#include "format.h" +#include "pyinterp.h" + +namespace ledger { + +void format_t::parse(const string& fmt) +{ + element_t * current = NULL; + + char buf[1024]; + char * q = buf; + + if (elements.size() > 0) + clear_elements(); + format_string = fmt; + + for (const char * p = fmt.c_str(); *p; p++) { + if (*p != '%' && *p != '\\') { + *q++ = *p; + continue; + } + else if (*p == '\\') { + p++; + switch (*p) { + case 'b': *q++ = '\b'; break; + case 'f': *q++ = '\f'; break; + case 'n': *q++ = '\n'; break; + case 'r': *q++ = '\r'; break; + case 't': *q++ = '\t'; break; + case 'v': *q++ = '\v'; break; + default: + *q++ = *p; + break; + } + continue; + } + else { + assert(*p == '%'); + if (*(p + 1) == '%') { + p++; // %% is the same as \% + *q++ = *p; + continue; + } + } + + current = new element_t; + elements.push_back(current); + + if (q != buf) { + current->kind = element_t::TEXT; + current->chars = new string(buf, q); + q = buf; + + current = new element_t; + elements.push_back(current); + } + + ++p; + if (*p == '-') { + current->align_left = true; + ++p; + } + + if (*p && std::isdigit(*p)) { + int num = *p++ - '0'; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->min_width = num; + } + + if (*p == '.') { + ++p; + int num = 0; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + + current->max_width = num; + if (current->min_width == -1) + current->min_width = current->max_width; + } + + if (current->max_width != -1 && current->min_width != -1 && + current->max_width < current->min_width) + throw_(format_exception, "Maximum width is less than the minimum width"); + + switch (*p) { + case '|': + current->kind = element_t::COLUMN; + break; + + case '{': + case '(': { + char open = *p; + char close = *p == '{' ? '}' : ')'; + ++p; + const char * b = p; + int depth = 1; + while (*p) { + if (*p == close && --depth == 0) + break; + else if (*p == open) + ++depth; + p++; + } + if (*p != close) + throw_(format_exception, "Missing '" << close << "'"); + + if (open == '{') { + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(string(b, p)); + } else { + assert(! current->format); + current->kind = element_t::GROUP; + current->format = new format_t(string(b, p)); + } + break; + } + + default: + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(string(p, p + 1)); + break; + } + } + + if (q != buf) { + current = new element_t; + elements.push_back(current); + + current->kind = element_t::TEXT; + current->chars = new string(buf, q); + } +} + +void format_t::compile(xml::node_t * context) +{ + for (std::list::iterator i = elements.begin(); + i != elements.end(); + i++) + switch ((*i)->kind) { + case element_t::XPATH: + assert((*i)->xpath); + (*i)->xpath->compile(context); + break; + case element_t::GROUP: + assert((*i)->format); + (*i)->format->compile(context); + break; + default: + break; + } +} + +int format_t::element_formatter_t::operator() + (std::ostream& out_str, element_t * elem, xml::node_t * context, + int column) const +{ + if (elem->kind == element_t::COLUMN) { + if (elem->max_width != -1 && elem->max_width < column) { + out_str << '\n'; + column = 0; + } + + if (elem->min_width != -1 && elem->min_width > column) { + out_str << string(elem->min_width - column, ' '); + column = elem->min_width; + } + return column; + } + + std::ostringstream out; + + if (elem->align_left) + out << std::left; + else + out << std::right; + + if (elem->min_width > 0) + out.width(elem->min_width); + + int start_column = column; + + if (elem->kind == element_t::XPATH) + elem->xpath->calc(context).strip_annotations() + .write(out, elem->min_width, elem->max_width); + else if (elem->kind == element_t::GROUP) + column = elem->format->format(out, context, column); + else if (elem->kind == element_t::TEXT) + out << *elem->chars; + else + assert(0); + + string temp = out.str(); + for (string::const_iterator i = temp.begin(); + i != temp.end(); + i++) + if (*i == '\n' || *i == '\r') + column = 0; + else + column++; + + int virtual_width = column - start_column; + + if (elem->min_width != -1 && virtual_width < elem->min_width) { + out_str << temp << string(' ', elem->min_width - virtual_width); + } + else if (elem->max_width != -1 && virtual_width > elem->max_width) { + temp.erase(temp.length() - (virtual_width - elem->max_width)); + out_str << temp; + } + else { + out_str << temp; + } + + return column; +} + +int format_t::format(std::ostream& out, xml::node_t * context, + int column, const element_formatter_t& formatter) const +{ + for (std::list::const_iterator i = elements.begin(); + i != elements.end(); + i++) + column = formatter(out, *i, context, column); + + return column; +} + +} // namespace ledger diff --git a/src/format.h b/src/format.h new file mode 100644 index 00000000..1ddd8202 --- /dev/null +++ b/src/format.h @@ -0,0 +1,108 @@ +#ifndef _FORMAT_H +#define _FORMAT_H + +#include "xpath.h" + +namespace ledger { + +class format_t +{ + public: + struct element_t + { + bool align_left; + short min_width; + short max_width; + + enum kind_t { UNKNOWN, TEXT, COLUMN, XPATH, GROUP } kind; + union { + string * chars; + xml::xpath_t * xpath; + format_t * format; + }; + + element_t() + : align_left(false), min_width(-1), max_width(-1), + kind(UNKNOWN), chars(NULL) { + TRACE_CTOR(element_t, ""); + } + + ~element_t() { + TRACE_DTOR(element_t); + + switch (kind) { + case TEXT: + delete chars; + break; + case XPATH: + delete xpath; + break; + case GROUP: + delete format; + break; + default: + assert(! chars); + break; + } + } + + private: + element_t(const element_t& other); + }; + + struct element_formatter_t { + virtual ~element_formatter_t() {} + virtual int operator()(std::ostream& out, element_t * element, + xml::node_t * context, int column) const; + }; + + string format_string; + std::list elements; + + private: + format_t(const format_t&); + + public: + format_t() { + TRACE_CTOR(format_t, ""); + } + format_t(const string& fmt) { + TRACE_CTOR(format_t, "const string&"); + parse(fmt); + } + + void clear_elements() { + for (std::list::iterator i = elements.begin(); + i != elements.end(); + i++) + delete *i; + elements.clear(); + } + + virtual ~format_t() { + TRACE_DTOR(format_t); + clear_elements(); + } + + void parse(const string& fmt); + + void compile(const string& fmt, xml::node_t * context = NULL) { + parse(fmt); + compile(context); + } + void compile(xml::node_t * context = NULL); + + int format(std::ostream& out, xml::node_t * context = NULL, + int column = 0, const element_formatter_t& formatter = + element_formatter_t()) const; + + operator bool() const { + return ! format_string.empty(); + } +}; + +DECLARE_EXCEPTION(format_exception); + +} // namespace ledger + +#endif // _FORMAT_H diff --git a/src/gd_qnan.h b/src/gd_qnan.h new file mode 100644 index 00000000..87eba8fb --- /dev/null +++ b/src/gd_qnan.h @@ -0,0 +1,12 @@ +#define f_QNAN 0xffc00000 +#define d_QNAN0 0x0 +#define d_QNAN1 0xfff80000 +#define ld_QNAN0 0x0 +#define ld_QNAN1 0xc0000000 +#define ld_QNAN2 0xffff +#define ld_QNAN3 0x0 +#define ldus_QNAN0 0x0 +#define ldus_QNAN1 0x0 +#define ldus_QNAN2 0x0 +#define ldus_QNAN3 0xc000 +#define ldus_QNAN4 0xffff diff --git a/src/gnucash.cc b/src/gnucash.cc new file mode 100644 index 00000000..abe8c555 --- /dev/null +++ b/src/gnucash.cc @@ -0,0 +1,366 @@ +#include "gnucash.h" + +namespace ledger { + +void startElement(void *userData, const char *name, const char ** /* attrs */) +{ + gnucash_parser_t * parser = static_cast(userData); + + if (std::strcmp(name, "gnc:account") == 0) { + parser->curr_account = new account_t(parser->master_account); + } + else if (std::strcmp(name, "act:name") == 0) + parser->action = gnucash_parser_t::ACCOUNT_NAME; + else if (std::strcmp(name, "act:id") == 0) + parser->action = gnucash_parser_t::ACCOUNT_ID; + else if (std::strcmp(name, "act:parent") == 0) + parser->action = gnucash_parser_t::ACCOUNT_PARENT; + else if (std::strcmp(name, "gnc:commodity") == 0) + parser->curr_comm = NULL; + else if (std::strcmp(name, "cmdty:id") == 0) + parser->action = gnucash_parser_t::COMM_SYM; + else if (std::strcmp(name, "cmdty:name") == 0) + parser->action = gnucash_parser_t::COMM_NAME; + else if (std::strcmp(name, "cmdty:fraction") == 0) + parser->action = gnucash_parser_t::COMM_PREC; + else if (std::strcmp(name, "gnc:transaction") == 0) { + assert(! parser->curr_entry); + parser->curr_entry = new entry_t; + } + else if (std::strcmp(name, "trn:num") == 0) + parser->action = gnucash_parser_t::ENTRY_NUM; + else if (std::strcmp(name, "trn:date-posted") == 0) + parser->action = gnucash_parser_t::ALMOST_ENTRY_DATE; + else if (parser->action == gnucash_parser_t::ALMOST_ENTRY_DATE && + std::strcmp(name, "ts:date") == 0) + parser->action = gnucash_parser_t::ENTRY_DATE; + else if (std::strcmp(name, "trn:description") == 0) + parser->action = gnucash_parser_t::ENTRY_DESC; + else if (std::strcmp(name, "trn:split") == 0) { + assert(parser->curr_entry); + parser->curr_entry->add_transaction(new transaction_t(parser->curr_account)); + } + else if (std::strcmp(name, "split:reconciled-state") == 0) + parser->action = gnucash_parser_t::XACT_STATE; + else if (std::strcmp(name, "split:amount") == 0) + parser->action = gnucash_parser_t::XACT_AMOUNT; + else if (std::strcmp(name, "split:value") == 0) + parser->action = gnucash_parser_t::XACT_VALUE; + else if (std::strcmp(name, "split:quantity") == 0) + parser->action = gnucash_parser_t::XACT_QUANTITY; + else if (std::strcmp(name, "split:account") == 0) + parser->action = gnucash_parser_t::XACT_ACCOUNT; + else if (std::strcmp(name, "split:memo") == 0) + parser->action = gnucash_parser_t::XACT_NOTE; +} + +void endElement(void *userData, const char *name) +{ + gnucash_parser_t * parser = static_cast(userData); + + if (std::strcmp(name, "gnc:account") == 0) { + assert(parser->curr_account); + if (parser->curr_account->parent == parser->master_account) + parser->curr_journal->add_account(parser->curr_account); + parser->accounts_by_id.insert(accounts_pair(parser->curr_account_id, + parser->curr_account)); + parser->curr_account = NULL; + } + else if (std::strcmp(name, "gnc:commodity") == 0) { + parser->curr_comm = NULL; + } + else if (std::strcmp(name, "gnc:transaction") == 0) { + assert(parser->curr_entry); + + // Add the new entry (what gnucash calls a 'transaction') to the + // journal + if (! parser->curr_journal->add_entry(parser->curr_entry)) { + print_entry(std::cerr, *parser->curr_entry); + parser->have_error = "The above entry does not balance"; + delete parser->curr_entry; + } else { + parser->curr_entry->src_idx = parser->src_idx; + parser->curr_entry->beg_pos = parser->beg_pos; + parser->curr_entry->beg_line = parser->beg_line; + parser->curr_entry->end_pos = parser->instreamp->tellg(); + parser->curr_entry->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + parser->count++; + } + + // Clear the relevant variables for the next run + parser->curr_entry = NULL; + parser->entry_comm = NULL; + } + else if (std::strcmp(name, "trn:split") == 0) { + transaction_t * xact = parser->curr_entry->transactions.back(); + + // Identify the commodity to use for the value of this + // transaction. The quantity indicates how many times that value + // the transaction is worth. + amount_t value; + commodity_t * default_commodity = NULL; + if (parser->entry_comm) { + default_commodity = parser->entry_comm; + } else { + gnucash_parser_t::account_comm_map::iterator ac = + parser->account_comms.find(xact->account); + if (ac != parser->account_comms.end()) + default_commodity = (*ac).second; + } + + if (default_commodity) { + parser->curr_quant.set_commodity(*default_commodity); + value = parser->curr_quant.round(); + + if (parser->curr_value.commodity() == *default_commodity) + parser->curr_value = value; + } else { + value = parser->curr_quant; + } + + xact->state = parser->curr_state; + xact->amount = value; + if (value != parser->curr_value) + xact->cost = new amount_t(parser->curr_value); + + xact->beg_pos = parser->beg_pos; + xact->beg_line = parser->beg_line; + xact->end_pos = parser->instreamp->tellg(); + xact->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + + // Clear the relevant variables for the next run + parser->curr_state = transaction_t::UNCLEARED; + parser->curr_value = amount_t(); + parser->curr_quant = amount_t(); + } + + parser->action = gnucash_parser_t::NO_ACTION; +} + +amount_t gnucash_parser_t::convert_number(const string& number, + int * precision) +{ + const char * num = number.c_str(); + + if (char * p = std::strchr(num, '/')) { + string numer_str(num, p - num); + string denom_str(p + 1); + + amount_t amt(numer_str); + amount_t den(denom_str); + + if (precision) + *precision = denom_str.length() - 1; + + if (! den) { + have_error = "Denominator in entry is zero!"; + return amt; + } else { + return amt / den; + } + } else { + return amount_t(number); + } +} + +void dataHandler(void *userData, const char *s, int len) +{ + gnucash_parser_t * parser = static_cast(userData); + + switch (parser->action) { + case gnucash_parser_t::ACCOUNT_NAME: + parser->curr_account->name = string(s, len); + break; + + case gnucash_parser_t::ACCOUNT_ID: + parser->curr_account_id = string(s, len); + break; + + case gnucash_parser_t::ACCOUNT_PARENT: { + accounts_map::iterator i = parser->accounts_by_id.find(string(s, len)); + assert(i != parser->accounts_by_id.end()); + parser->curr_account->parent = (*i).second; + parser->curr_account->depth = parser->curr_account->parent->depth + 1; + (*i).second->add_account(parser->curr_account); + break; + } + + case gnucash_parser_t::COMM_SYM: { + string symbol(s, len); + if (symbol == "USD") symbol = "$"; + + parser->curr_comm = commodity_t::find_or_create(symbol); + assert(parser->curr_comm); + + if (symbol != "$") + parser->curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); + + if (parser->curr_account) + parser->account_comms.insert + (gnucash_parser_t::account_comm_pair(parser->curr_account, + parser->curr_comm)); + else if (parser->curr_entry) + parser->entry_comm = parser->curr_comm; + break; + } + + case gnucash_parser_t::COMM_NAME: + parser->curr_comm->set_name(string(s, len)); + break; + + case gnucash_parser_t::COMM_PREC: + parser->curr_comm->set_precision(len - 1); + break; + + case gnucash_parser_t::ENTRY_NUM: + parser->curr_entry->code = string(s, len); + break; + + case gnucash_parser_t::ENTRY_DATE: + parser->curr_entry->_date = parse_datetime(string(s, len)); + break; + + case gnucash_parser_t::ENTRY_DESC: + parser->curr_entry->payee = string(s, len); + break; + + case gnucash_parser_t::XACT_STATE: + if (*s == 'y') + parser->curr_state = transaction_t::CLEARED; + else if (*s == 'n') + parser->curr_state = transaction_t::UNCLEARED; + else + parser->curr_state = transaction_t::PENDING; + break; + + case gnucash_parser_t::XACT_VALUE: { + int precision; + assert(parser->entry_comm); + parser->curr_value = parser->convert_number(string(s, len), &precision); + parser->curr_value.set_commodity(*parser->entry_comm); + + if (precision > parser->entry_comm->precision()) + parser->entry_comm->set_precision(precision); + break; + } + + case gnucash_parser_t::XACT_QUANTITY: + parser->curr_quant = parser->convert_number(string(s, len)); + break; + + case gnucash_parser_t::XACT_ACCOUNT: { + transaction_t * xact = parser->curr_entry->transactions.back(); + + accounts_map::iterator i = + parser->accounts_by_id.find(string(s, len)); + if (i != parser->accounts_by_id.end()) { + xact->account = (*i).second; + } else { + xact->account = parser->curr_journal->find_account(""); + + parser->have_error = (string("Could not find account ") + + string(s, len)); + } + break; + } + + case gnucash_parser_t::XACT_NOTE: + parser->curr_entry->transactions.back()->note = string(s, len); + break; + + case gnucash_parser_t::NO_ACTION: + case gnucash_parser_t::ALMOST_ENTRY_DATE: + case gnucash_parser_t::XACT_AMOUNT: + break; + + default: + assert(0); + break; + } +} + +bool gnucash_parser_t::test(std::istream& in) const +{ + char buf[5]; + in.read(buf, 5); + in.clear(); + in.seekg(0, std::ios::beg); + + return std::strncmp(buf, "master; + curr_account = NULL; + curr_entry = NULL; + curr_comm = NULL; + entry_comm = NULL; + curr_state = transaction_t::UNCLEARED; + + instreamp = ∈ + path = original_file ? *original_file : ""; + 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("$"); + usd->set_precision(2); + usd->add_flags(COMMODITY_STYLE_THOUSANDS); + + offset = 2; + expat_parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); + + while (in.good() && ! in.eof()) { + beg_pos = in.tellg(); + beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1; + + 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++; + const char * msg = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw_(parse_exception, msg); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; +#if 0 + // jww (2007-04-26): What is this doing? + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; +#endif + have_error = ""; + } + } + + XML_ParserFree(parser); + + accounts_by_id.clear(); + curr_account_id.clear(); + + return count; +} + +} // namespace ledger diff --git a/src/gnucash.h b/src/gnucash.h new file mode 100644 index 00000000..a0d9fb18 --- /dev/null +++ b/src/gnucash.h @@ -0,0 +1,75 @@ +#ifndef _GNUCASH_H +#define _GNUCASH_H + +#include "parser.h" +#include "journal.h" + +namespace ledger { + +struct gnucash_parser_t : public parser_t +{ + typedef std::map accounts_map; + typedef std::pair accounts_pair; + + typedef std::map account_comm_map; + typedef std::pair account_comm_pair; + + journal_t * curr_journal; + account_t * master_account; + account_t * curr_account; + string curr_account_id; + entry_t * curr_entry; + commodity_t * entry_comm; + commodity_t * curr_comm; + amount_t curr_value; + amount_t curr_quant; + XML_Parser expat_parser; + accounts_map accounts_by_id; + account_comm_map account_comms; + unsigned int count; + string have_error; + + std::istream * instreamp; + unsigned int offset; + XML_Parser parser; + string path; + unsigned int src_idx; + unsigned long beg_pos; + unsigned long beg_line; + + transaction_t::state_t curr_state; + + enum action_t { + NO_ACTION, + ACCOUNT_NAME, + ACCOUNT_ID, + ACCOUNT_PARENT, + COMM_SYM, + COMM_NAME, + COMM_PREC, + ENTRY_NUM, + ALMOST_ENTRY_DATE, + ENTRY_DATE, + ENTRY_DESC, + XACT_STATE, + XACT_AMOUNT, + XACT_VALUE, + XACT_QUANTITY, + XACT_ACCOUNT, + XACT_NOTE + } action; + + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); + + amount_t convert_number(const string& number, int * precision = NULL); +}; + +} // namespace ledger + +#endif // _GNUCASH_H diff --git a/src/journal.cc b/src/journal.cc new file mode 100644 index 00000000..1f643364 --- /dev/null +++ b/src/journal.cc @@ -0,0 +1,667 @@ +#include "journal.h" +#include "mask.h" +#if 0 +#ifdef USE_BOOST_PYTHON +#include "pyinterp.h" +#endif +#endif + +namespace ledger { + +const string version = PACKAGE_VERSION; + +bool transaction_t::use_effective_date = false; + +transaction_t::~transaction_t() +{ + TRACE_DTOR(transaction_t); + if (cost) delete cost; +} + +moment_t transaction_t::actual_date() const +{ + if (! is_valid_moment(_date) && entry) + return entry->actual_date(); + return _date; +} + +moment_t transaction_t::effective_date() const +{ + if (! is_valid_moment(_date_eff) && entry) + return entry->effective_date(); + return _date_eff; +} + +bool transaction_t::valid() const +{ + if (! entry) { + DEBUG_("ledger.validate", "transaction_t: ! entry"); + return false; + } + + if (state != UNCLEARED && state != CLEARED && state != PENDING) { + DEBUG_("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_("ledger.validate", "transaction_t: ! found"); + return false; + } + + if (! account) { + DEBUG_("ledger.validate", "transaction_t: ! account"); + return false; + } + + if (! amount.valid()) { + DEBUG_("ledger.validate", "transaction_t: ! amount.valid()"); + return false; + } + + if (cost && ! cost->valid()) { + DEBUG_("ledger.validate", "transaction_t: cost && ! cost->valid()"); + return false; + } + + if (flags & ~0x003f) { + DEBUG_("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 + ((*x)->amount.commodity())); + if (ann_comm.price) + balance += ann_comm.price * (*x)->amount.number() - *((*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++; + + amount_t per_unit_cost = + amount_t((*other_bal).second / (*this_bal).second.number()).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(this); + + if ((*x)->amount.commodity() && + ! (*x)->amount.commodity().annotated) + (*x)->amount.annotate_commodity + (per_unit_cost.abs(), + entry ? entry->actual_date() : moment_t(), + entry ? entry->code : ""); + + (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount.number())); + 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_(exception, "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.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)->negate(); + (*x)->flags |= TRANSACTION_CALCULATED; + + balance += (*x)->amount; + break; + + default: + break; + } + } + + if (balance) { +#if 1 + throw_(balance_exception, "Entry does not balance"); +#else + 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 + } + + 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), data(NULL) +{ + TRACE_CTOR(entry_t, "copy"); + 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 (! is_valid_moment(_date) || ! journal) { + DEBUG_("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_("ledger.validate", "entry_t: transaction not valid"); + return false; + } + + return true; +} + +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++) { + // jww (2006-09-10): Create a scope here based on entry + if (predicate.calc((xml::node_t *) NULL)) { + 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; + 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); + entry.add_transaction(xact); + } + } + } +} + +account_t::~account_t() +{ + TRACE_DTOR(account_t); + + for (accounts_map::iterator i = accounts.begin(); + i != accounts.end(); + i++) + delete (*i).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); + account->journal = journal; + + std::pair 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 string& regexp) +{ + return find_account_re_(master, mask_t(regexp)); +} + +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; +} + +bool account_t::valid() const +{ + if (depth > 256 || ! journal) { + DEBUG_("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_("ledger.validate", "account_t: parent refers to itself!"); + return false; + } + + if (! (*i).second->valid()) { + DEBUG_("ledger.validate", "account_t: child not valid"); + return false; + } + } + + return true; +} + +journal_t::~journal_t() +{ + TRACE_DTOR(journal_t); + + assert(master); + delete master; + + if (document) + delete document; + + // 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.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; + } + + for (entries_list::const_iterator i = entries.begin(); + i != entries.end(); + i++) + if (! (*i)->valid()) { + DEBUG_("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_("ledger.validate", "journal_t: commodity not valid"); + return false; + } + + return true; +} + +void print_entry(std::ostream& out, const entry_base_t& entry_base, + const string& prefix) +{ + string print_format; + + if (dynamic_cast(&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(&entry_base)) { + out << "= " << entry->predicate.expr << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else if (const period_entry_t * entry = + dynamic_cast(&entry_base)) { + out << "~ " << entry->period_string << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else { + assert(0); + } + +#if 0 + format_entries formatter(out, print_format); + walk_transactions(const_cast(entry_base.transactions), + formatter); + formatter.flush(); + + clear_transaction_xdata cleaner; + walk_transactions(const_cast(entry_base.transactions), + cleaner); +#endif +} + +#if 0 +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 string& desc) throw() + : file_context("", 0, desc), xact(_xact) +{ + const ledger::strings_list& sources(xact.entry->journal->sources); + unsigned 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; +} +#endif + +} // namespace ledger diff --git a/src/journal.h b/src/journal.h new file mode 100644 index 00000000..1995e0f3 --- /dev/null +++ b/src/journal.h @@ -0,0 +1,471 @@ +#ifndef _JOURNAL_H +#define _JOURNAL_H + +#include "xpath.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; + moment_t _date; + moment_t _date_eff; + account_t * account; + amount_t amount; + string amount_expr; + amount_t * cost; + string cost_expr; + state_t state; + unsigned short flags; + string note; + unsigned long beg_pos; + unsigned long beg_line; + unsigned long 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) { + TRACE_CTOR(transaction_t, "account_t *"); + } + transaction_t(account_t * _account, + const amount_t& _amount, + unsigned int _flags = TRANSACTION_NORMAL, + const 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) { + TRACE_CTOR(transaction_t, "account_t *, const amount_t&, unsigned int, const string&"); + } + 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) { + TRACE_CTOR(transaction_t, "copy"); + } + ~transaction_t(); + + moment_t actual_date() const; + moment_t effective_date() const; + moment_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; +}; + +#if 0 +class xact_context : public file_context { + public: + const transaction_t& xact; + + xact_context(const transaction_t& _xact, + const string& desc = "") throw(); + virtual ~xact_context() throw() {} +}; +#endif + +class journal_t; + +typedef std::list transactions_list; + +class entry_base_t +{ + public: + journal_t * journal; + unsigned long src_idx; + unsigned long beg_pos; + unsigned long beg_line; + unsigned long 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) { + TRACE_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) + { + TRACE_CTOR(entry_base_t, "copy"); + 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() { + TRACE_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: + moment_t _date; + moment_t _date_eff; + string code; + string payee; + + mutable void * data; + + entry_t() : data(NULL) { + TRACE_CTOR(entry_t, ""); + } + entry_t(const entry_t& e); + + virtual ~entry_t() { + TRACE_DTOR(entry_t); + } + + moment_t actual_date() const { + return _date; + } + moment_t effective_date() const { + if (! is_valid_moment(_date_eff)) + return _date; + return _date_eff; + } + moment_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; +}; + +void print_entry(std::ostream& out, const entry_base_t& entry, + const string& prefix = ""); + +#if 0 +class entry_context : public error_context { + public: + const entry_base_t& entry; + + entry_context(const entry_base_t& _entry, + const string& _desc = "") throw() + : error_context(_desc), entry(_entry) {} + virtual ~entry_context() throw() {} + + virtual void describe(std::ostream& out) const throw(); +}; +#endif + +DECLARE_EXCEPTION(balance_exception); + + +class auto_entry_t : public entry_base_t +{ +public: + xml::xpath_t predicate; + + auto_entry_t() { + TRACE_CTOR(auto_entry_t, ""); + } + 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_t * _journal) : journal(_journal) {} + 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 string& _period) + : period(_period), period_string(_period) { + TRACE_CTOR(period_entry_t, "const string&"); + } + 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"); + } + + virtual ~period_entry_t() { + TRACE_DTOR(period_entry_t); + } + + virtual bool valid() const { + return period; + } +}; + + +typedef std::map accounts_map; +typedef std::pair accounts_pair; + +class account_t +{ + public: + typedef unsigned long ident_t; + + journal_t * journal; + account_t * parent; + string name; + 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 string& _note = "") + : 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(); + + bool operator==(const account_t& account) { + return this == &account; + } + bool operator!=(const account_t& account) { + return ! (*this == account); + } + + 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 string& name, bool auto_create = true); + + operator 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) : + entry_finalizer_t(), func(other.func) {} + virtual bool operator()(entry_t& entry, bool post) { + return func(entry, post); + } +}; + +template +void add_hook(std::list& list, T obj, const bool prepend = false) { + if (prepend) + list.push_front(obj); + else + list.push_back(obj); +} + +template +void remove_hook(std::list& list, T obj) { + list.remove(obj); +} + +template +bool run_hooks(std::list& list, Data& item, bool post) { + for (typename std::list::const_iterator i = list.begin(); + i != list.end(); + i++) + if (! (*(*i))(item, post)) + return false; + return true; +} + + +typedef std::list entries_list; +typedef std::list auto_entries_list; +typedef std::list period_entries_list; +typedef std::list strings_list; + +class session_t; + +class journal_t +{ + public: + session_t * session; + account_t * master; + account_t * basket; + entries_list entries; + strings_list sources; + string price_db; + char * item_pool; + char * item_pool_end; + + // This is used for dynamically representing the journal data as an + // XML tree, to facilitate transformations without modifying any of + // the underlying structures (the transformers modify the XML tree + // -- perhaps even adding, changing or deleting nodes -- but they do + // not affect the basic data parsed from the journal file). + xml::document_t * document; + + auto_entries_list auto_entries; + period_entries_list period_entries; + mutable accounts_map accounts_cache; + + std::list entry_finalize_hooks; + + journal_t(session_t * _session) + : session(_session), basket(NULL), + item_pool(NULL), item_pool_end(NULL), document(NULL) { + TRACE_CTOR(journal_t, ""); + master = new account_t(NULL, ""); + master->journal = this; + } + ~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 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 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_finalize_hooks, finalizer); + } + void remove_entry_finalizer(entry_finalizer_t * finalizer) { + remove_hook(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 string version; + +} // namespace ledger + +#endif // _JOURNAL_H diff --git a/src/ledger.h b/src/ledger.h new file mode 100644 index 00000000..2122ece3 --- /dev/null +++ b/src/ledger.h @@ -0,0 +1,42 @@ +#ifndef _LEDGER_H +#define _LEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003,2004 John Wiegley +// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#if 0 +#include +#include +#include +#include +#endif + +#endif // _LEDGER_H diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..0ca984c3 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,484 @@ +#if defined(USE_BOOST_PYTHON) +#include +#else +#include +#endif +#include + +#include "acconf.h" + +#ifdef HAVE_UNIX_PIPES +#include +#include +#include "fdstream.hpp" +#endif + +using namespace ledger; + +#if 0 +class print_addr : public repitem_t::select_callback_t { + virtual void operator()(repitem_t * item) { + std::cout << item << std::endl; + } +}; +#endif + +static int read_and_report(report_t * report, int argc, char * argv[], + char * envp[]) +{ + session_t& session(*report->session); + + // Handle the command-line arguments + + std::list args; + process_arguments(argc - 1, argv + 1, false, report, args); + + if (args.empty()) { +#if 0 + help(std::cerr); +#endif + 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.empty(); + + 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(envp), "LEDGER_", report); + TRACE_FINISH(environment, 1); + + const char * p = std::getenv("HOME"); + string home = p ? p : ""; + + if (session.init_file.empty()) + session.init_file = home + "/.ledgerrc"; + if (session.price_db.empty()) + session.price_db = home + "/.pricedb"; + + if (session.cache_file.empty()) + session.cache_file = home + "/.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); + INFO("Price database is " << session.price_db); + INFO("Binary cache is " << session.cache_file); + INFO("Journal file is " << session.data_file); + + if (! session.use_cache) + INFO("Binary cache mechanism will not be used"); + + // Read the command word and create a command object based on it + + string verb = *arg++; + + std::auto_ptr command; + + if (verb == "register" || verb == "reg" || verb == "r") { +#if 1 + command.reset(new register_command); +#else + command = new format_command + ("register", either_or(report->format_string, + report->session->register_format)); +#endif + } +#if 0 + else if (verb == "balance" || verb == "bal" || verb == "b") { + if (! report->raw_mode) { + report->transforms.push_back(new accounts_transform); + report->transforms.push_back(new clean_transform); + report->transforms.push_back(new compact_transform); + } + command = new format_command + ("balance", either_or(report->format_string, + report->session->balance_format)); + } + else if (verb == "print" || verb == "p") { + if (! report->raw_mode) + report->transforms.push_back(new optimize_transform); + command = new format_command + ("print", either_or(report->format_string, + report->session->print_format)); + } + else if (verb == "equity") { + if (! report->raw_mode) + report->transforms.push_back(new accounts_transform); + command = new format_command + ("equity", either_or(report->format_string, + report->session->equity_format)); + } + else if (verb == "entry") + command = new entry_command; + else if (verb == "dump") + command = new dump_command; + else if (verb == "output") + command = new output_command; + else if (verb == "prices") + command = new prices_command; + else if (verb == "pricesdb") + command = new pricesdb_command; + else if (verb == "csv") + command = new csv_command; + else if (verb == "emacs" || verb == "lisp") + command = new emacs_command; +#endif + else if (verb == "xml") + command.reset(new xml_command); + else if (verb == "expr") + ; + else if (verb == "xpath") + ; + else if (verb == "parse") { + xml::xpath_t expr(*arg); + + IF_INFO() { + std::cout << "Value expression tree:" << std::endl; + expr.dump(std::cout); + std::cout << std::endl; + std::cout << "Value expression parsed was:" << std::endl; + expr.write(std::cout); + std::cout << std::endl << std::endl; + std::cout << "Result of calculation: "; + } + + std::cout << expr.calc((xml::document_t *)NULL, report). + strip_annotations() << std::endl; + + return 0; + } + else { + char buf[128]; + std::strcpy(buf, "command_"); + std::strcat(buf, verb.c_str()); + + // jww (2007-04-19): This is an error, since command is an + // auto_ptr! + if (xml::xpath_t::op_t * def = report->lookup(buf)) + command.reset(def->functor_obj()); + + if (! command.get()) + throw_(exception, string("Unrecognized command '") + verb + "'"); + } + + // 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.read_data(report->account); + INFO_FINISH(journal); + + 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); + + // 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 (! report->pager.empty()) { + status = pipe(pfd); + if (status == -1) + throw_(exception, "Failed to create pipe"); + + status = fork(); + if (status < 0) { + throw_(exception, "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(report->pager.c_str(), '/'); + if (arg0) + arg0++; + else + arg0 = report->pager.c_str(); // No slashes in pager. + + execlp(report->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 expr commands? Do so now. + + if (verb == "expr") { + xml::xpath_t expr(*arg); + + IF_INFO() { + *out << "Value expression tree:" << std::endl; + expr.dump(*out); + *out << std::endl; + *out << "Value expression parsed was:" << std::endl; + expr.write(*out); + *out << std::endl << std::endl; + *out << "Result of calculation: "; + } + + *out << expr.calc((xml::document_t *)NULL, report). + strip_annotations() << std::endl; + + return 0; + } + else if (verb == "xpath") { + std::cout << "XPath parsed:" << std::endl; + xml::xpath_t xpath(*arg); + xpath.write(*out); + *out << std::endl; + +#if 0 + std::auto_ptr items(repitem_t::wrap(&session, report, true)); + print_addr cb; + items->select(path.get(), cb); +#endif + return 0; + } + + // Create the an argument scope containing the report command's + // arguments, and then invoke the command. + + std::auto_ptr locals + (new xml::xpath_t::scope_t(report, xml::xpath_t::scope_t::ARGUMENT)); + + locals->args = new value_t::sequence_t; + locals->args.push_back(out); + locals->args.push_back(journal->document); + + if (command->wants_args) { + for (strings_list::iterator i = args.begin(); + i != args.end(); + i++) + locals->args.push_back(*i); + } else { + string regexps[4]; + + // Treat the remaining command-line arguments as regular + // expressions, used for refining report results. + + int base = 0; + for (strings_list::iterator i = arg; i != args.end(); i++) + if ((*i)[0] == '-') { + if ((*i)[1] == '-') { + if (base == 0) + base += 2; + continue; + } + if (! regexps[base + 1].empty()) + regexps[base + 1] += "|"; + regexps[base + 1] += (*i).substr(1); + } else { + if (! regexps[base].empty()) + regexps[base] += "|"; + regexps[base] += *i; + } + +#if 0 + // jww (2006-09-21): Escape the \ in these strings! + + if (! regexps[3].empty()) + report->transforms.push_front + (new remove_transform + (string("//entry[payee =~ /(") + regexps[3] + ")/]")); + + if (! regexps[2].empty()) + report->transforms.push_front + (new select_transform + (string("//entry[payee =~ /(") + regexps[2] + ")/]")); + + if (! regexps[1].empty()) + report->transforms.push_front + (new remove_transform + (string("//xact[account =~ /(") + regexps[1] + ")/]")); + + if (! regexps[0].empty()) + report->transforms.push_front + (new select_transform + (string("//xact[account =~ /(") + regexps[0] + ")/]")); +#endif + } + + INFO_START(transforms, "Applied transforms"); + report->apply_transforms(journal->document); + INFO_FINISH(transforms); + + INFO_START(command, "Did user command '" << verb << "'"); + value_t temp; + (*command)(temp, locals.get()); + INFO_FINISH(command); + + // Write out the binary cache, if need be + + if (session.use_cache && session.cache_dirty && + ! session.cache_file.empty()) { + TRACE_START(binary_cache, 1, "Wrote binary journal file"); + + std::ofstream stream(session.cache_file.c_str()); +#if 0 + write_binary_journal(stream, journal); +#endif + + TRACE_FINISH(binary_cache, 1); + } + +#if defined(FREE_MEMORY) + // Cleanup memory -- if this is a beta or development build. + + if (! report->output_file.empty()) + delete out; +#endif + + // If the user specified a pager, wait for it to exit now + +#ifdef HAVE_UNIX_PIPES + if (report->output_file.empty() && ! report->pager.empty()) { + delete out; + close(pfd[1]); + + // Wait for child to finish + wait(&status); + if (status & 0xffff != 0) + throw_(exception, "Something went wrong in the pager"); + } +#endif + + 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 defined(VERIFY_ON) + if (std::strcmp(argv[i], "--verify") == 0) + ledger::verify_enabled = true; +#endif +#if defined(DEBUG_ON) + if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { + ledger::_log_level = LOG_DEBUG; + ledger::_log_category = argv[i + 1]; + i++; + } +#endif +#if defined(TRACING_ON) + if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { + ledger::_log_level = LOG_TRACE; + ledger::_trace_level = std::atoi(argv[i + 1]); + i++; + } +#endif + } + + try { + std::ios::sync_with_stdio(false); + + ledger::initialize(); + +#if ! defined(FULL_DEBUG) + ledger::do_cleanup = false; +#endif + INFO("Ledger starting"); + + std::auto_ptr session(new ledger::session_t); + +#if 0 + session->register_parser(new binary_parser_t); +#endif +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + session->register_parser(new xml::xml_parser_t); + session->register_parser(new gnucash_parser_t); +#endif +#ifdef HAVE_LIBOFX + session->register_parser(new ofx_parser_t); +#endif + session->register_parser(new qif_parser_t); + session->register_parser(new textual_parser_t); + + std::auto_ptr report(new ledger::report_t(session.get())); + + status = read_and_report(report.get(), argc, argv, envp); + + if (! ledger::do_cleanup) { + report.release(); + session.release(); + } + } +#if 0 + catch (error * err) { + std::cout.flush(); + // Push a null here since there's no file context + if (err->context.empty() || + ! dynamic_cast(err->context.front())) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Error"); + std::cerr << err->what() << std::endl; + delete err; + } + catch (fatal * err) { + std::cout.flush(); + // Push a null here since there's no file context + if (err->context.empty() || + ! dynamic_cast(err->context.front())) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Fatal"); + std::cerr << err->what() << std::endl; + delete err; + } +#endif + catch (const std::exception& err) { + std::cout.flush(); + std::cerr << "Error: " << err.what() << std::endl; + } + catch (int _status) { + status = _status; + } + + if (ledger::do_cleanup) + ledger::shutdown(); + + return status; +} + +// main.cc ends here. diff --git a/src/mask.cc b/src/mask.cc new file mode 100644 index 00000000..02cac880 --- /dev/null +++ b/src/mask.cc @@ -0,0 +1,24 @@ +#include "mask.h" + +namespace ledger { + +mask_t::mask_t(const 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++; + } + + expr.assign(p); +} + +} // namespace ledger diff --git a/src/mask.h b/src/mask.h new file mode 100644 index 00000000..82634c19 --- /dev/null +++ b/src/mask.h @@ -0,0 +1,26 @@ +#ifndef _MASK_H +#define _MASK_H + +#include "utils.h" + +#include + +namespace ledger { + +class mask_t +{ + public: + bool exclude; + boost::regex expr; + + explicit mask_t(const string& pattern); + mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {} + + bool match(const string& str) const { + return boost::regex_match(str, expr) && ! exclude; + } +}; + +} // namespace ledger + +#endif // _MASK_H diff --git a/src/ofx.cc b/src/ofx.cc new file mode 100644 index 00000000..f0bce1f2 --- /dev/null +++ b/src/ofx.cc @@ -0,0 +1,218 @@ +#include "ofx.h" + +namespace ledger { + +typedef std::map accounts_map; +typedef std::pair accounts_pair; + +typedef std::map commodities_map; +typedef std::pair 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_("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 << " " << default_commodity->base_symbol(); + xact->cost = new amount_t(stream.str()); + } + + DEBUG_("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 , since it is not specified. + account = curr_journal->find_account(""); + entry->add_transaction(new transaction_t(account)); + + if (! curr_journal->add_entry(entry)) { + print_entry(std::cerr, *entry); +#if 0 + // jww (2005-02-09): uncomment + have_error = "The above entry does not balance"; +#endif + delete entry; + return -1; + } + return 0; +} + +int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data) +{ + 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 = 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_("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); + 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, "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/src/ofx.h b/src/ofx.h new file mode 100644 index 00000000..54713c17 --- /dev/null +++ b/src/ofx.h @@ -0,0 +1,21 @@ +#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, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +} // namespace ledger + +#endif // _OFX_H diff --git a/src/option.cc b/src/option.cc new file mode 100644 index 00000000..222baf92 --- /dev/null +++ b/src/option.cc @@ -0,0 +1,219 @@ +#include "option.h" + +#if 0 +#ifdef USE_BOOST_PYTHON +static ledger::option_t * find_option(const string& name); +#endif +#endif + +namespace ledger { + +namespace { + xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope, + const string& name) + { + char buf[128]; + std::strcpy(buf, "option_"); + char * p = &buf[7]; + for (const char * q = name.c_str(); *q; q++) { + if (*q == '-') + *p++ = '_'; + else + *p++ = *q; + } + *p = '\0'; + + return scope->lookup(buf); + } + + xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope, + const char letter) + { + char buf[9]; + std::strcpy(buf, "option_"); + buf[7] = letter; + buf[8] = '\0'; + + return scope->lookup(buf); + } + + void process_option(xml::xpath_t::functor_t * opt, xml::xpath_t::scope_t * scope, + const char * arg) + { +#if 0 + try { +#endif + std::auto_ptr args; + if (arg) { + args.reset(new xml::xpath_t::scope_t(scope, xml::xpath_t::scope_t::ARGUMENT)); + args->args.set_string(arg); + } + + value_t temp; + (*opt)(temp, args.get()); +#if 0 + } + catch (error * err) { + err->context.push_back + (new error_context + (string("While parsing option '--") + opt->long_opt + + "'" + (opt->short_opt != '\0' ? + (string(" (-") + opt->short_opt + "):") : ":"))); + throw err; + } +#endif + } +} + +bool process_option(const string& name, xml::xpath_t::scope_t * scope, + const char * arg) +{ + std::auto_ptr opt(find_option(scope, name)); + if (opt.get()) { + xml::xpath_t::functor_t * def = opt->functor_obj(); + if (def) { + process_option(def, scope, arg); + return true; + } + } + return false; +} + +void process_environment(const char ** envp, const string& tag, + xml::xpath_t::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 == '=') { +#if 0 + try { +#endif + if (! process_option(buf, scope, q + 1)) +#if 0 + throw new option_error("unknown option") +#endif + ; +#if 0 + } + catch (error * err) { + err->context.push_back + (new error_context + (string("While parsing environment variable option '") + + *p + "':")); + throw err; + } +#endif + } + } +} + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t * scope, + std::list& 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 + 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; + } + + std::auto_ptr opt(find_option(scope, name)); + if (! opt.get()) + throw_(option_exception, "illegal option --" << name); + + xml::xpath_t::functor_t * def = opt->functor_obj(); + if (! def) + throw_(option_exception, "illegal option --" << name); + + if (def->wants_args && value == NULL) { + value = *++i; + if (value == NULL) + throw_(option_exception, "missing option argument for --" << name); + } + process_option(def, scope, value); + } + else if ((*i)[1] == '\0') { + throw_(option_exception, "illegal option -"); + } + else { + std::list option_queue; + + int x = 1; + for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { + xml::xpath_t::op_t * opt = find_option(scope, c); + if (! opt) + throw_(option_exception, "illegal option -" << c); + + xml::xpath_t::functor_t * def = opt->functor_obj(); + if (! def) + throw_(option_exception, "illegal option -" << c); + + option_queue.push_back(opt); + } + + for (std::list::iterator + o = option_queue.begin(); + o != option_queue.end(); + o++) { + char * value = NULL; + + xml::xpath_t::functor_t * def = (*o)->functor_obj(); + assert(def); + + if (def->wants_args) { + value = *++i; + if (value == NULL) + throw_(option_exception, "missing option argument for -" << +#if 0 + def->short_opt +#else + '?' +#endif + ); + } + process_option(def, scope, value); + + delete *o; + } + } + + next: + ; + } +} + +} // namespace ledger diff --git a/src/option.h b/src/option.h new file mode 100644 index 00000000..c9664ae3 --- /dev/null +++ b/src/option.h @@ -0,0 +1,22 @@ +#ifndef _OPTION_H +#define _OPTION_H + +#include "xpath.h" + +namespace ledger { + +bool process_option(const string& name, xml::xpath_t::scope_t * scope, + const char * arg = NULL); + +void process_environment(const char ** envp, const string& tag, + xml::xpath_t::scope_t * scope); + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t * scope, + std::list& args); + +DECLARE_EXCEPTION(option_exception); + +} // namespace ledger + +#endif // _OPTION_H diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 00000000..2bdc4622 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,86 @@ +#ifndef _PARSER_H +#define _PARSER_H + +#include "utils.h" + +namespace ledger { + +class account_t; +class journal_t; + +class parser_t +{ + public: + virtual ~parser_t() {} + + virtual bool test(std::istream& in) const = 0; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL) = 0; +}; + +DECLARE_EXCEPTION(parse_exception); + +/************************************************************************ + * + * General utility parsing functions + */ + +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'; \ +} + +#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 // _PARSER_H diff --git a/src/py_amount.cc b/src/py_amount.cc new file mode 100644 index 00000000..1d9f9255 --- /dev/null +++ b/src/py_amount.cc @@ -0,0 +1,245 @@ +#include "pyinterp.h" +#include "amount.h" + +using namespace boost::python; + +namespace ledger { + +int py_amount_quantity(amount_t& amount) +{ + std::ostringstream quant; + amount.print_quantity(quant); + return std::atol(quant.str().c_str()); +} + +void py_parse_1(amount_t& amount, const string& str, + unsigned char flags) { + amount.parse(str, flags); +} +void py_parse_2(amount_t& amount, const string& str) { + amount.parse(str); +} + +amount_t py_round_1(amount_t& amount, unsigned int prec) { + return amount.round(prec); +} +amount_t py_round_2(amount_t& amount) { + return amount.round(); +} + +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 moment_t& moment, + const moment_t& date, + const moment_t& last, + amount_t& price) { + call_method(self, "__call__", commodity, moment, date, last, price); + } +}; + +commodity_t * py_find_commodity(const string& symbol) +{ + return commodity_t::find(symbol); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_ArithmeticError, err.what()); \ + } + +EXC_TRANSLATOR(amount_exception) + +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()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + + .def(self += self) + .def(self += long()) + .def(self += double()) + + .def(self + self) + .def(self + long()) + .def(long() + self) + .def(self + double()) + .def(double() + self) + + .def(self -= self) + .def(self -= long()) + .def(self -= double()) + + .def(self - self) + .def(self - long()) + .def(long() - self) + .def(self - double()) + .def(double() - self) + + .def(self *= self) + .def(self *= long()) + .def(self *= double()) + + .def(self * self) + .def(self * long()) + .def(long() * self) + .def(self * double()) + .def(double() * self) + + .def(self /= self) + .def(self /= long()) + .def(self /= double()) + + .def(self / self) + .def(self / long()) + .def(long() / self) + .def(self / double()) + .def(double() / self) + + .def(- self) + + .def(self < self) + .def(self < long()) + .def(long() < self) + + .def(self <= self) + .def(self <= long()) + .def(long() <= self) + + .def(self > self) + .def(self > long()) + .def(long() > self) + + .def(self >= self) + .def(self >= long()) + .def(long() >= self) + + .def(self == self) + .def(self == long()) + .def(long() == self) + + .def(self != self) + .def(self != long()) + .def(long() != self) + + .def(! self) + + .def(self_ns::int_(self)) + .def(self_ns::float_(self)) + + .def("__str__", &amount_t::to_string) + .def("__repr__", &amount_t::to_fullstring) + + .def("has_commodity", &amount_t::has_commodity) + + .add_property("commodity", + make_function(&amount_t::commodity, + return_value_policy()), + make_function(&amount_t::set_commodity, + with_custodian_and_ward<1, 2>())) + + .def("annotate_commodity", &amount_t::annotate_commodity) + .def("strip_annotations", &amount_t::strip_annotations) + .def("clear_commodity", &amount_t::clear_commodity) + + //.add_static_property("full_strings", &amount_t::full_strings) + + .def("to_string", &amount_t::to_string) + .def("to_fullstring", &amount_t::to_fullstring) + .def("quantity_string", &amount_t::quantity_string) + + .def("exact", &amount_t::exact) + .staticmethod("exact") + + .def("__abs__", &amount_t::abs) + .def("compare", &amount_t::compare) + .def("date", &amount_t::date) + .def("negate", &amount_t::negate) + .def("null", &amount_t::null) + .def("parse", py_parse_1) + .def("parse", py_parse_2) + .def("price", &amount_t::price) + .def("realzero", &amount_t::realzero) + .def("reduce", &amount_t::reduce) + .def("round", py_round_1) + .def("round", py_round_2) + .def("sign", &amount_t::sign) + .def("unround", &amount_t::unround) + .def("value", &amount_t::value) + .def("zero", &amount_t::zero) + + .def("valid", &amount_t::valid) + + .def("initialize", &amount_t::initialize) + .staticmethod("initialize") + .def("shutdown", &amount_t::shutdown) + .staticmethod("shutdown") + ; + + 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") +#if 0 + .add_property("symbol", &commodity_t::symbol) + + .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) + //.add_property("updater", &commodity_t::updater) + + .add_property("smaller", + make_getter(&commodity_t::smaller, + return_value_policy()), + make_setter(&commodity_t::smaller, + return_value_policy())) + .add_property("larger", + make_getter(&commodity_t::larger, + return_value_policy()), + make_setter(&commodity_t::larger, + return_value_policy())) + + .def(self_ns::str(self)) + + .def("find", py_find_commodity, + return_value_policy()) + .staticmethod("find") + + .def("add_price", &commodity_t::add_price) + .def("remove_price", &commodity_t::remove_price) + .def("value", &commodity_t::value) + + .def("valid", &commodity_t::valid) +#endif + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(amount_exception); +} + +} // namespace ledger diff --git a/src/py_balance.cc b/src/py_balance.cc new file mode 100644 index 00000000..372bf1e9 --- /dev/null +++ b/src/py_balance.cc @@ -0,0 +1,202 @@ +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()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + + .def(self += self) + .def(self += other()) + .def(self += long()) + .def(self + self) + .def(self + other()) + .def(self + long()) + .def(self -= self) + .def(self -= other()) + .def(self -= long()) + .def(self - self) + .def(self - other()) + .def(self - long()) + .def(self *= self) + .def(self *= other()) + .def(self *= long()) + .def(self * self) + .def(self * other()) + .def(self * long()) + .def(self /= self) + .def(self /= other()) + .def(self /= long()) + .def(self / self) + .def(self / other()) + .def(self / long()) + .def(- self) + + .def(self < self) + .def(self < other()) + .def(self < long()) + .def(self <= self) + .def(self <= other()) + .def(self <= long()) + .def(self > self) + .def(self > other()) + .def(self > long()) + .def(self >= self) + .def(self >= other()) + .def(self >= long()) + .def(self == self) + .def(self == other()) + .def(self == long()) + .def(self != self) + .def(self != other()) + .def(self != long()) + .def(! self) + + .def(self_ns::str(self)) + + .def("__abs__", &balance_t::abs) + .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()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + + .def(self += self) + .def(self += other()) + .def(self += other()) + .def(self += long()) + .def(self + self) + .def(self + other()) + .def(self + other()) + .def(self + long()) + .def(self -= self) + .def(self -= other()) + .def(self -= other()) + .def(self -= long()) + .def(self - self) + .def(self - other()) + .def(self - other()) + .def(self - long()) + .def(self *= self) + .def(self *= other()) + .def(self *= other()) + .def(self *= long()) + .def(self * self) + .def(self * other()) + .def(self * other()) + .def(self * long()) + .def(self /= self) + .def(self /= other()) + .def(self /= other()) + .def(self /= long()) + .def(self / self) + .def(self / other()) + .def(self / other()) + .def(self / long()) + .def(- self) + + .def(self < self) + .def(self < other()) + .def(self < other()) + .def(self < long()) + .def(self <= self) + .def(self <= other()) + .def(self <= other()) + .def(self <= long()) + .def(self > self) + .def(self > other()) + .def(self > other()) + .def(self > long()) + .def(self >= self) + .def(self >= other()) + .def(self >= other()) + .def(self >= long()) + .def(self == self) + .def(self == other()) + .def(self == other()) + .def(self == long()) + .def(self != self) + .def(self != other()) + .def(self != other()) + .def(self != long()) + .def(! self) + + .def(self_ns::str(self)) + + .def("__abs__", &balance_pair_t::abs) + .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())) + ; +} diff --git a/src/py_format.cc b/src/py_format.cc new file mode 100644 index 00000000..e2faf5dc --- /dev/null +++ b/src/py_format.cc @@ -0,0 +1,11 @@ +using namespace boost::python; +using namespace ledger; + +void export_format() +{ + class_< format_t > ("Format") + .def(init()) + .def("parse", &format_t::parse) + .def("format", &format_t::format) + ; +} diff --git a/src/py_journal.cc b/src/py_journal.cc new file mode 100644 index 00000000..d309cf95 --- /dev/null +++ b/src/py_journal.cc @@ -0,0 +1,374 @@ +using namespace boost::python; +using namespace ledger; + +entry_t& transaction_entry(const transaction_t& xact) +{ + return *xact.entry; +} + +unsigned int transactions_len(entry_base_t& entry) +{ + return entry.transactions.size(); +} + +transaction_t& transactions_getitem(entry_base_t& entry, int i) +{ + static int last_index = 0; + static entry_base_t * last_entry = NULL; + static transactions_list::iterator elem; + + std::size_t len = entry.transactions.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&entry == last_entry && i == last_index + 1) { + last_index = i; + return **++elem; + } + + int x = i < 0 ? len + i : i; + elem = entry.transactions.begin(); + while (--x >= 0) + elem++; + + last_entry = &entry; + last_index = i; + + return **elem; +} + +unsigned int entries_len(journal_t& journal) +{ + return journal.entries.size(); +} + +entry_t& entries_getitem(journal_t& journal, int i) +{ + static int last_index = 0; + static journal_t * last_journal = NULL; + static entries_list::iterator elem; + + std::size_t len = journal.entries.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&journal == last_journal && i == last_index + 1) { + last_index = i; + return **++elem; + } + + int x = i < 0 ? len + i : i; + elem = journal.entries.begin(); + while (--x >= 0) + elem++; + + last_journal = &journal; + last_index = i; + + return **elem; +} + +unsigned int accounts_len(account_t& account) +{ + return account.accounts.size(); +} + +account_t& accounts_getitem(account_t& account, int i) +{ + static int last_index = 0; + static account_t * last_account = NULL; + static accounts_map::iterator elem; + + std::size_t len = account.accounts.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&account == last_account && i == last_index + 1) { + last_index = i; + return *(*++elem).second; + } + + int x = i < 0 ? len + i : i; + elem = account.accounts.begin(); + while (--x >= 0) + elem++; + + last_account = &account; + last_index = i; + + return *(*elem).second; +} + +PyObject * py_account_get_data(account_t& account) +{ + return (PyObject *) account.data; +} + +void py_account_set_data(account_t& account, PyObject * obj) +{ + account.data = obj; +} + +account_t * py_find_account_1(journal_t& journal, const string& name) +{ + return journal.find_account(name); +} + +account_t * py_find_account_2(journal_t& journal, const string& name, + const bool auto_create) +{ + return journal.find_account(name, auto_create); +} + +bool py_add_entry(journal_t& journal, entry_t * entry) { + return journal.add_entry(new entry_t(*entry)); +} + +void py_add_transaction(entry_base_t& entry, transaction_t * xact) { + return entry.add_transaction(new transaction_t(*xact)); +} + +struct entry_base_wrap : public entry_base_t +{ + PyObject * self; + entry_base_wrap(PyObject * self_) : self(self_) {} + + virtual bool valid() const { + return call_method(self, "valid"); + } +}; + +struct py_entry_finalizer_t : public entry_finalizer_t { + object pyobj; + py_entry_finalizer_t() {} + py_entry_finalizer_t(object obj) : pyobj(obj) {} + py_entry_finalizer_t(const py_entry_finalizer_t& other) + : pyobj(other.pyobj) {} + virtual bool operator()(entry_t& entry, bool post) { + return call(pyobj.ptr(), entry, post); + } +}; + +std::list py_finalizers; + +void py_add_entry_finalizer(journal_t& journal, object x) +{ + py_finalizers.push_back(py_entry_finalizer_t(x)); + journal.add_entry_finalizer(&py_finalizers.back()); +} + +void py_remove_entry_finalizer(journal_t& journal, object x) +{ + for (std::list::iterator i = py_finalizers.begin(); + i != py_finalizers.end(); + i++) + if ((*i).pyobj == x) { + journal.remove_entry_finalizer(&(*i)); + py_finalizers.erase(i); + return; + } +} + +void py_run_entry_finalizers(journal_t& journal, entry_t& entry, bool post) +{ + run_hooks(journal.entry_finalize_hooks, entry, post); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(balance_error) +EXC_TRANSLATOR(interval_expr_error) +EXC_TRANSLATOR(format_error) +EXC_TRANSLATOR(parse_error) + +value_t py_transaction_amount(transaction_t * xact) { + return value_t(xact->amount); +} + +transaction_t::state_t py_entry_state(entry_t * entry) { + transaction_t::state_t state; + if (entry->get_state(&state)) + return state; + else + return transaction_t::UNCLEARED; +} + +void export_journal() +{ + scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; + scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; + scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; + scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; + scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; + scope().attr("TRANSACTION_CALCULATED") = TRANSACTION_CALCULATED; + + enum_< transaction_t::state_t > ("State") + .value("Uncleared", transaction_t::UNCLEARED) + .value("Cleared", transaction_t::CLEARED) + .value("Pending", transaction_t::PENDING) + ; + + class_< transaction_t > ("Transaction") + .def(init >()) + .def(init >()) + + .def(self == self) + .def(self != self) + + .add_property("entry", + make_getter(&transaction_t::entry, + return_value_policy())) + .add_property("account", + make_getter(&transaction_t::account, + return_value_policy())) + + .add_property("amount", &py_transaction_amount) + .def_readonly("amount_expr", &transaction_t::amount_expr) + .add_property("cost", + make_getter(&transaction_t::cost, + return_internal_reference<1>())) + .def_readonly("cost_expr", &transaction_t::cost_expr) + + .def_readwrite("state", &transaction_t::state) + .def_readwrite("flags", &transaction_t::flags) + .def_readwrite("note", &transaction_t::note) + + .def_readonly("beg_pos", &transaction_t::beg_pos) + .def_readonly("beg_line", &transaction_t::beg_line) + .def_readonly("end_pos", &transaction_t::end_pos) + .def_readonly("end_line", &transaction_t::end_line) + + .def("actual_date", &transaction_t::actual_date) + .def("effective_date", &transaction_t::effective_date) + .def("date", &transaction_t::date) + + .def("use_effective_date", &transaction_t::use_effective_date) + + .def("valid", &transaction_t::valid) + ; + + class_< account_t > + ("Account", + init >() + [with_custodian_and_ward<1, 2>()]) + .def(self == self) + .def(self != self) + + .def(self_ns::str(self)) + + .def("__len__", accounts_len) + .def("__getitem__", accounts_getitem, return_internal_reference<1>()) + + .add_property("journal", + make_getter(&account_t::journal, + return_value_policy())) + .add_property("parent", + make_getter(&account_t::parent, + return_value_policy())) + .def_readwrite("name", &account_t::name) + .def_readwrite("note", &account_t::note) + .def_readonly("depth", &account_t::depth) + .add_property("data", py_account_get_data, py_account_set_data) + .def_readonly("ident", &account_t::ident) + + .def("fullname", &account_t::fullname) + + .def("add_account", &account_t::add_account) + .def("remove_account", &account_t::remove_account) + + .def("find_account", &account_t::find_account, + return_value_policy()) + + .def("valid", &account_t::valid) + ; + + class_< journal_t > ("Journal") + .def(self == self) + .def(self != self) + + .def("__len__", entries_len) + .def("__getitem__", entries_getitem, return_internal_reference<1>()) + + .add_property("master", make_getter(&journal_t::master, + return_internal_reference<1>())) + .add_property("basket", make_getter(&journal_t::basket, + return_internal_reference<1>())) + + .def_readonly("sources", &journal_t::sources) + + .def_readwrite("price_db", &journal_t::price_db) + + .def("add_account", &journal_t::add_account) + .def("remove_account", &journal_t::remove_account) + + .def("find_account", py_find_account_1, return_internal_reference<1>()) + .def("find_account", py_find_account_2, return_internal_reference<1>()) + .def("find_account_re", &journal_t::find_account_re, + return_internal_reference<1>()) + + .def("add_entry", py_add_entry) + .def("remove_entry", &journal_t::remove_entry) + + .def("add_entry_finalizer", py_add_entry_finalizer) + .def("remove_entry_finalizer", py_remove_entry_finalizer) + .def("run_entry_finalizers", py_run_entry_finalizers) + + .def("valid", &journal_t::valid) + ; + + class_< entry_base_t, entry_base_wrap, boost::noncopyable > ("EntryBase") + .def("__len__", transactions_len) + .def("__getitem__", transactions_getitem, + return_internal_reference<1>()) + + .def_readonly("journal", &entry_base_t::journal) + + .def_readonly("src_idx", &entry_base_t::src_idx) + .def_readonly("beg_pos", &entry_base_t::beg_pos) + .def_readonly("beg_line", &entry_base_t::beg_line) + .def_readonly("end_pos", &entry_base_t::end_pos) + .def_readonly("end_line", &entry_base_t::end_line) + + .def("add_transaction", py_add_transaction) + .def("remove_transaction", &entry_base_t::remove_transaction) + + .def(self == self) + .def(self != self) + + .def("finalize", &entry_base_t::finalize) + .def("valid", &entry_base_t::valid) + ; + + class_< entry_t, bases > ("Entry") + .add_property("date", &entry_t::date) + .add_property("effective_date", &entry_t::effective_date) + .add_property("actual_date", &entry_t::actual_date) + + .def_readwrite("code", &entry_t::code) + .def_readwrite("payee", &entry_t::payee) + + .add_property("state", &py_entry_state) + + .def("valid", &entry_t::valid) + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(balance_error); + EXC_TRANSLATE(interval_expr_error); + EXC_TRANSLATE(format_error); + EXC_TRANSLATE(parse_error); +} diff --git a/src/py_option.cc b/src/py_option.cc new file mode 100644 index 00000000..877d92a7 --- /dev/null +++ b/src/py_option.cc @@ -0,0 +1,73 @@ +using namespace boost::python; +using namespace ledger; + +struct py_option_t : public option_t +{ + PyObject * self; + + py_option_t(PyObject * self_, + const string& long_opt, + const bool wants_arg) + : self(self_), option_t(long_opt, wants_arg) {} + + virtual ~py_option_t() {} + + virtual bool check(option_source_t source) { + return call_method(self, "check", source); + } + + virtual void select(report_t * report, const char * optarg = NULL) { + if (optarg) + return call_method(self, "select", report, optarg); + else + return call_method(self, "select", report); + } +}; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(option_select_overloads, + py_option_t::select, 1, 2) + +typedef std::map options_map; +typedef std::pair options_pair; + +options_map options; + +static option_t * find_option(const string& name) +{ + options_map::const_iterator i = options.find(name); + if (i != options.end()) + return extract((*i).second.ptr()); + + return NULL; +} + +void shutdown_option() +{ + options.clear(); +} + +void export_option() +{ + class_< option_t, py_option_t, boost::noncopyable > + ("Option", init()) + .def_readonly("long_opt", &py_option_t::long_opt) + .def_readonly("short_opt", &py_option_t::short_opt) + .def_readonly("wants_arg", &py_option_t::wants_arg) + .def_readwrite("handled", &py_option_t::handled) + .def("check", &py_option_t::check) + .def("select", &py_option_t::select, option_select_overloads()) + ; + + enum_< option_t::option_source_t > ("OptionSource") + .value("InitFile", option_t::INIT_FILE) + .value("Environment", option_t::ENVIRONMENT) + .value("DataFile", option_t::DATA_FILE) + .value("CommandLine", option_t::COMMAND_LINE) + ; + + class_< options_map > ("OptionsMap") + .def(map_indexing_suite()) + ; + + scope().attr("options") = ptr(&options); +} diff --git a/src/py_parser.cc b/src/py_parser.cc new file mode 100644 index 00000000..f119a0ef --- /dev/null +++ b/src/py_parser.cc @@ -0,0 +1,48 @@ +#include "parser.h" + +#if 0 +#ifdef USE_BOOST_PYTHON + +using namespace boost::python; +using namespace ledger; + +struct py_parser_t : public parser_t +{ + PyObject * self; + py_parser_t(PyObject * self_) : self(self_) {} + + virtual bool test(std::istream& in) const { + return call_method(self, "test", in); + } + + virtual repitem_t * parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL) { + return call_method(self, "parse", in, journal, master, + original_file); + } +}; + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(parser_parse_overloads, + py_parser_t::parse, 2, 4) + +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4) +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads, + parse_journal_file, 2, 4) + +void export_parser() { + class_< parser_t, py_parser_t, boost::noncopyable > ("Parser") + .def("test", &py_parser_t::test) + .def("parse", &py_parser_t::parse, parser_parse_overloads()) + ; + + def("register_parser", register_parser); + def("unregister_parser", unregister_parser); + + def("parse_journal", parse_journal, parse_journal_overloads()); + def("parse_journal_file", parse_journal_file, parse_journal_file_overloads()); +} + +#endif // USE_BOOST_PYTHON +#endif diff --git a/src/py_report.cc b/src/py_report.cc new file mode 100644 index 00000000..0bc36857 --- /dev/null +++ b/src/py_report.cc @@ -0,0 +1,13 @@ +using namespace boost::python; +using namespace ledger; + +void export_report() +{ + class_< report_t > ("Report") + .add_property("session", + make_getter(&report_t::session, + return_value_policy())) + + .def("apply_transforms", &report_t::apply_transforms) + ; +} diff --git a/src/py_session.cc b/src/py_session.cc new file mode 100644 index 00000000..f249c54e --- /dev/null +++ b/src/py_session.cc @@ -0,0 +1,36 @@ +using namespace boost::python; +using namespace ledger; + +void export_session() +{ + class_< session_t > ("Session") + .def_readwrite("init_file", &session_t::init_file) + .def_readwrite("data_file", &session_t::data_file) + .def_readwrite("cache_file", &session_t::cache_file) + .def_readwrite("price_db", &session_t::price_db) + + .def_readwrite("balance_format", &session_t::balance_format) + .def_readwrite("register_format", &session_t::register_format) + .def_readwrite("wide_register_format", &session_t::wide_register_format) + .def_readwrite("plot_amount_format", &session_t::plot_amount_format) + .def_readwrite("plot_total_format", &session_t::plot_total_format) + .def_readwrite("print_format", &session_t::print_format) + .def_readwrite("write_hdr_format", &session_t::write_hdr_format) + .def_readwrite("write_xact_format", &session_t::write_xact_format) + .def_readwrite("equity_format", &session_t::equity_format) + .def_readwrite("prices_format", &session_t::prices_format) + .def_readwrite("pricesdb_format", &session_t::pricesdb_format) + + .def_readwrite("pricing_leeway", &session_t::pricing_leeway) + + .def_readwrite("download_quotes", &session_t::download_quotes) + .def_readwrite("use_cache", &session_t::use_cache) + .def_readwrite("cache_dirty", &session_t::cache_dirty) + .def_readwrite("debug_mode", &session_t::debug_mode) + .def_readwrite("verbose_mode", &session_t::verbose_mode) + .def_readwrite("trace_alloc_mode", &session_t::trace_alloc_mode) + .def_readwrite("trace_class_mode", &session_t::trace_class_mode) + + .def_readwrite("journals", &session_t::journals) + ; +} diff --git a/src/py_transform.cc b/src/py_transform.cc new file mode 100644 index 00000000..a0ba31d4 --- /dev/null +++ b/src/py_transform.cc @@ -0,0 +1,8 @@ +using namespace boost::python; +using namespace ledger; + +void export_transform() +{ + class_< repitem_t > ("Transform") + ; +} diff --git a/src/py_value.cc b/src/py_value.cc new file mode 100644 index 00000000..1a43ebc1 --- /dev/null +++ b/src/py_value.cc @@ -0,0 +1,337 @@ +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& val) +{ + switch (val.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 *) val.data)); + + case value_t::BALANCE_PAIR: + return balance_pair_len(*((balance_pair_t *) val.data)); + + case value_t::STRING: + case value_t::XML_NODE: + case value_t::POINTER: + return 1; + + case value_t::SEQUENCE: + return (*(value_t::sequence_t **) val.data)->size(); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +amount_t value_getitem(value_t& val, int i) +{ + std::size_t len = value_len(val); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + switch (val.type) { + case value_t::BOOLEAN: + throw_(value_exception, "Cannot cast a boolean to an amount"); + + case value_t::INTEGER: + return long(val); + + case value_t::DATETIME: + throw_(value_exception, "Cannot cast a date/time to an amount"); + + case value_t::AMOUNT: + return *((amount_t *) val.data); + + case value_t::BALANCE: + return balance_getitem(*((balance_t *) val.data), i); + + case value_t::BALANCE_PAIR: + return balance_pair_getitem(*((balance_pair_t *) val.data), i); + + case value_t::STRING: + throw_(value_exception, "Cannot cast a string to an amount"); + + case value_t::XML_NODE: + return (*(xml::node_t **) data)->to_value(); + + case value_t::POINTER: + throw_(value_exception, "Cannot cast a pointer to an amount"); + + case value_t::SEQUENCE: + return (*(value_t::sequence_t **) val.data)[i]; + + default: + assert(0); + break; + } + assert(0); + return 0L; +} + +double py_to_float(value_t& val) +{ + return double(val); +} + +void export_value() +{ + class_< value_t > ("value") + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(init()) + .def(initmoment_t()) + + .def(self + self) + .def(self + other()) + .def(self + other()) + .def(self + other()) + .def(self + other()) + .def(self + long()) + .def(self + double()) + + .def(other() + self) + .def(other() + self) + .def(other() + self) + .def(other() + self) + .def(long() + self) + .def(double() + self) + + .def(self - self) + .def(self - other()) + .def(self - other()) + .def(self - other()) + .def(self - other()) + .def(self - long()) + .def(self - double()) + + .def(other() - self) + .def(other() - self) + .def(other() - self) + .def(other() - self) + .def(long() - self) + .def(double() - self) + + .def(self * self) + .def(self * other()) + .def(self * other()) + .def(self * other()) + .def(self * other()) + .def(self * long()) + .def(self * double()) + + .def(other() * self) + .def(other() * self) + .def(other() * self) + .def(other() * self) + .def(long() * self) + .def(double() * self) + + .def(self / self) + .def(self / other()) + .def(self / other()) + .def(self / other()) + .def(self / other()) + .def(self / long()) + .def(self / double()) + + .def(other() / self) + .def(other() / self) + .def(other() / self) + .def(other() / self) + .def(long() / self) + .def(double() / self) + + .def(- self) + + .def(self += self) + .def(self += other()) + .def(self += other()) + .def(self += other()) + .def(self += other()) + .def(self += long()) + .def(self += double()) + + .def(self -= self) + .def(self -= other()) + .def(self -= other()) + .def(self -= other()) + .def(self -= other()) + .def(self -= long()) + .def(self -= double()) + + .def(self *= self) + .def(self *= other()) + .def(self *= other()) + .def(self *= other()) + .def(self *= other()) + .def(self *= long()) + .def(self *= double()) + + .def(self /= self) + .def(self /= other()) + .def(self /= other()) + .def(self /= other()) + .def(self /= other()) + .def(self /= long()) + .def(self /= double()) + + .def(self < self) + .def(self < other()) + .def(self < other()) + .def(self < other()) + .def(self < other()) + .def(self < long()) + .def(self < othermoment_t()) + .def(self < double()) + + .def(other() < self) + .def(other() < self) + .def(other() < self) + .def(other() < self) + .def(long() < self) + .def(othermoment_t() < self) + .def(double() < self) + + .def(self <= self) + .def(self <= other()) + .def(self <= other()) + .def(self <= other()) + .def(self <= other()) + .def(self <= long()) + .def(self <= othermoment_t()) + .def(self <= double()) + + .def(other() <= self) + .def(other() <= self) + .def(other() <= self) + .def(other() <= self) + .def(long() <= self) + .def(othermoment_t() <= self) + .def(double() <= self) + + .def(self > self) + .def(self > other()) + .def(self > other()) + .def(self > other()) + .def(self > other()) + .def(self > long()) + .def(self > othermoment_t()) + .def(self > double()) + + .def(other() > self) + .def(other() > self) + .def(other() > self) + .def(other() > self) + .def(long() > self) + .def(othermoment_t() > self) + .def(double() > self) + + .def(self >= self) + .def(self >= other()) + .def(self >= other()) + .def(self >= other()) + .def(self >= other()) + .def(self >= long()) + .def(self >= othermoment_t()) + .def(self >= double()) + + .def(other() >= self) + .def(other() >= self) + .def(other() >= self) + .def(other() >= self) + .def(long() >= self) + .def(othermoment_t() >= self) + .def(double() >= self) + + .def(self == self) + .def(self == other()) + .def(self == other()) + .def(self == other()) + .def(self == other()) + .def(self == long()) + .def(self == othermoment_t()) + .def(self == double()) + + .def(other() == self) + .def(other() == self) + .def(other() == self) + .def(other() == self) + .def(long() == self) + .def(othermoment_t() == self) + .def(double() == self) + + .def(self != self) + .def(self != other()) + .def(self != other()) + .def(self != other()) + .def(self != other()) + .def(self != long()) + .def(self != othermoment_t()) + .def(self != double()) + + .def(other() != self) + .def(other() != self) + .def(other() != self) + .def(other() != self) + .def(long() != self) + .def(othermoment_t() != self) + .def(double() != self) + + .def(! self) + + .def(self_ns::int_(self)) + .def(self_ns::float_(self)) + .def(self_ns::str(self)) + + .def_readonly("type", &value_t::type) + + .def("__abs__", &value_t::abs) + .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("write", &value_t::write) + ; + + 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("BalancePair", value_t::BALANCE_PAIR) + .value("String", value_t::STRING) + .value("XmlNode", value_t::XML_NODE) + .value("Pointer", value_t::POINTER) + .value("Sequence", value_t::SEQUENCE) + ; +} diff --git a/src/py_xpath.cc b/src/py_xpath.cc new file mode 100644 index 00000000..6569ce7b --- /dev/null +++ b/src/py_xpath.cc @@ -0,0 +1,79 @@ +using namespace boost::python; +using namespace ledger; + +value_t py_calc_1(xpath_t::op_t& xpath_t, const details_t& item) +{ + value_t result; + xpath_t.calc(result, item); + return result; +} + +template +value_t py_calc(xpath_t::op_t& xpath_t, const T& item) +{ + value_t result; + xpath_t.calc(result, details_t(item)); + return result; +} + +xpath_t::op_t * py_parse_xpath_t_1(const string& str) +{ + return parse_xpath_t(str); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(xpath_t_error) +EXC_TRANSLATOR(calc_error) +#if 0 +EXC_TRANSLATOR(mask_error) +#endif + +void export_xpath() +{ + class_< details_t > ("Details", init()) + .def(init()) + .def(init()) + .add_property("entry", + make_getter(&details_t::entry, + return_value_policy())) + .add_property("xact", + make_getter(&details_t::xact, + return_value_policy())) + .add_property("account", + make_getter(&details_t::account, + return_value_policy())) + ; + + class_< xpath_t::op_t > ("ValueExpr", init()) + .def("calc", py_calc_1) + .def("calc", py_calc) + .def("calc", py_calc) + .def("calc", py_calc) + ; + + def("parse_xpath_t", py_parse_xpath_t_1, + return_value_policy()); + + class_< item_predicate > + ("TransactionPredicate", init()) + .def("__call__", &item_predicate::operator()) + ; + + class_< item_predicate > + ("AccountPredicate", init()) + .def("__call__", &item_predicate::operator()) + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator(&exc_translate_ ## type); + + EXC_TRANSLATE(xpath_t_error); + EXC_TRANSLATE(calc_error); +#if 0 + EXC_TRANSLATE(mask_error); +#endif +} diff --git a/src/pyfstream.h b/src/pyfstream.h new file mode 100644 index 00000000..27e5166b --- /dev/null +++ b/src/pyfstream.h @@ -0,0 +1,138 @@ +#ifndef _PYFSTREAM_H +#define _PYFSTREAM_H + +// pyofstream +// - a stream that writes on a Python file object + +class pyoutbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + public: + // constructor + pyoutbuf (PyFileObject * _fo) : fo(_fo) {} + + 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, (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, (PyObject *)fo) < 0) + num = 0; + delete[] buf; + return num; + } +}; + +class pyofstream : public std::ostream { + protected: + pyoutbuf buf; + public: + pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { + rdbuf(&buf); + } +}; + +// pyifstream +// - a stream that reads on a file descriptor + +class pyinbuf : public std::streambuf { + 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) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + 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((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 std::istream { + protected: + pyinbuf buf; + public: + pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { + rdbuf(&buf); + } +}; + +#endif // _PYFSTREAM_H diff --git a/src/pyinterp.cc b/src/pyinterp.cc new file mode 100644 index 00000000..b0cb166c --- /dev/null +++ b/src/pyinterp.cc @@ -0,0 +1,164 @@ +#include "pyinterp.h" + +namespace ledger { + +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; + } +}; + +extern void initialize_for_python(); + +python_interpreter_t::python_interpreter_t(xml::xpath_t::scope_t * parent) + : xml::xpath_t::scope_t(parent), + mmodule(borrowed(PyImport_AddModule("__main__"))), + nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) +{ + Py_Initialize(); + 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_(exception, "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_(exception, "Importing Python module " << str); + } +} + +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_(exception, "Evaluating Python code"); + } +} + +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_(exception, "Evaluating Python code"); + } +} + +void python_interpreter_t::functor_t::operator()(value_t& result, + xml::xpath_t::scope_t * locals) +{ + try { + if (! PyCallable_Check(func.ptr())) { + result = static_cast(extract(func.ptr())); + } else { + assert(locals->args.type == value_t::SEQUENCE); + if (locals->args.to_sequence()->size() > 0) { + list arglist; + for (value_t::sequence_t::iterator + i = locals->args.to_sequence()->begin(); + i != locals->args.to_sequence()->end(); + i++) + arglist.append(*i); + + if (PyObject * val = + PyObject_CallObject(func.ptr(), tuple(arglist).ptr())) { + result = extract(val)(); + Py_DECREF(val); + } + else if (PyObject * err = PyErr_Occurred()) { + PyErr_Print(); + throw_(xml::xpath_t::calc_exception, + "While calling Python function '" << name() << "'"); + } else { + assert(0); + } + } else { + result = call(func.ptr()); + } + } + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(xml::xpath_t::calc_exception, + "While calling Python function '" << name() << "'"); + } +} + +void python_interpreter_t::lambda_t::operator()(value_t& result, + xml::xpath_t::scope_t * locals) +{ + try { + assert(locals->args.type == value_t::SEQUENCE); + assert(locals->args.to_sequence()->size() == 1); + value_t item = locals->args[0]; + assert(item.type == value_t::POINTER); + result = call(func.ptr(), (xml::node_t *)*(void **)item.data); + } + catch (const error_already_set&) { + PyErr_Print(); + throw_(xml::xpath_t::calc_exception, + "While evaluating Python lambda expression"); + } +} + +} // namespace ledger diff --git a/src/pyinterp.h b/src/pyinterp.h new file mode 100644 index 00000000..a75ef78b --- /dev/null +++ b/src/pyinterp.h @@ -0,0 +1,83 @@ +#ifndef _PY_EVAL_H +#define _PY_EVAL_H + +#include "xpath.h" + +#if defined(USE_BOOST_PYTHON) + +#include +#include +#include +#include + +#include + +#include "pyfstream.h" + +using namespace boost::python; + +namespace ledger { + +class python_interpreter_t : public xml::xpath_t::scope_t +{ + handle<> mmodule; + + public: + dict nspace; + + python_interpreter_t(xml::xpath_t::scope_t * parent); + + virtual ~python_interpreter_t() { + Py_Finalize(); + } + + object import(const string& name); + + enum py_eval_mode_t { + PY_EVAL_EXPR, + PY_EVAL_STMT, + PY_EVAL_MULTI + }; + + object eval(std::istream& in, py_eval_mode_t mode = PY_EVAL_EXPR); + object eval(const string& str, py_eval_mode_t mode = PY_EVAL_EXPR); + 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 : public xml::xpath_t::functor_t { + protected: + object func; + public: + functor_t(const string& name, object _func) + : xml::xpath_t::functor_t(name), func(_func) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); + }; + + virtual void define(const string& name, xml::xpath_t::op_t * def) { + // Pass any definitions up to our parent + parent->define(name, def); + } + + virtual xml::xpath_t::op_t * lookup(const string& name) { + object func = eval(name); + if (! func) + return parent ? parent->lookup(name) : NULL; + return xml::xpath_t::wrap_functor(new functor_t(name, func)); + } + + class lambda_t : public functor_t { + public: + lambda_t(object code) : functor_t("", code) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); + }; +}; + +} // namespace ledger + +#endif // USE_BOOST_PYTHON + +#endif // _PY_EVAL_H diff --git a/src/pyledger.cc b/src/pyledger.cc new file mode 100644 index 00000000..b7654e5d --- /dev/null +++ b/src/pyledger.cc @@ -0,0 +1,53 @@ +#include + +using namespace boost::python; + +namespace ledger { + +void export_amount(); +#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(); + +void shutdown_option(); +#endif + +void initialize_for_python() +{ + export_amount(); +#if 0 + export_balance(); + export_value(); + + export_journal(); + export_parser(); + export_option(); + export_walk(); + export_format(); + export_report(); + export_valexpr(); +#endif +} + +void shutdown_for_python() +{ +#if 0 + shutdown_option(); +#endif +} + +} + +BOOST_PYTHON_MODULE(ledger) +{ + ledger::initialize(); + ledger::initialize_for_python(); +} diff --git a/src/pyledger.h b/src/pyledger.h new file mode 100644 index 00000000..4056fec0 --- /dev/null +++ b/src/pyledger.h @@ -0,0 +1,16 @@ +#ifndef _PYLEDGER_H +#define _PYLEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool (with Python support via Boost.Python) +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003,2004 John Wiegley +// + +#include +#include + +#endif // _PYLEDGER_H diff --git a/src/qif.cc b/src/qif.cc new file mode 100644 index 00000000..5e567ea0 --- /dev/null +++ b/src/qif.cc @@ -0,0 +1,243 @@ +#include "qif.h" +#include "journal.h" + +namespace ledger { + +#define MAX_LINE 1024 + +static char line[MAX_LINE + 1]; +static string path; +static unsigned int src_idx; +static unsigned int linenum; + +static inline char * get_line(std::istream& in) { + in.getline(line, MAX_LINE); + int len = std::strlen(line); + if (line[len - 1] == '\r') + line[len - 1] = '\0'; + linenum++; + return line; +} + +bool qif_parser_t::test(std::istream& in) const +{ + char magic[sizeof(unsigned int) + 1]; + in.read(magic, sizeof(unsigned int)); + magic[sizeof(unsigned int)] = '\0'; + in.clear(); + in.seekg(0, std::ios::beg); + + return (std::strcmp(magic, "!Typ") == 0 || + std::strcmp(magic, "\n!Ty") == 0 || + std::strcmp(magic, "\r\n!T") == 0); +} + +unsigned int qif_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string *) +{ + std::auto_ptr entry; + std::auto_ptr 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; + + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + path = journal->sources.back(); + src_idx = journal->sources.size() - 1; + linenum = 1; + + unsigned long beg_pos = 0; + unsigned long beg_line = 0; + +#define SET_BEG_POS_AND_LINE() \ + if (! beg_line) { \ + beg_pos = in.tellg(); \ + beg_line = linenum; \ + } + + while (in.good() && ! in.eof()) { + char c; + in.get(c); + switch (c) { + case ' ': + case '\t': + if (peek_next_nonws(in) != '\n') { + get_line(in); + throw_(parse_exception, "Line begins with whitespace"); + } + // fall through... + + case '\n': + linenum++; + case '\r': // skip blank lines + break; + + case '!': + get_line(in); + + if (std::strcmp(line, "Type:Invst") == 0 || + std::strcmp(line, "Account") == 0 || + std::strcmp(line, "Type:Cat") == 0 || + std::strcmp(line, "Type:Class") == 0 || + std::strcmp(line, "Type:Memorized") == 0) + throw_(parse_exception, + "QIF files of type " << line << " are not supported."); + break; + + case 'D': + SET_BEG_POS_AND_LINE(); + get_line(in); + entry->_date = parse_datetime(line); + break; + + case 'T': + case '$': { + SET_BEG_POS_AND_LINE(); + get_line(in); + xact->amount.parse(line); + + unsigned char flags = xact->amount.commodity().flags(); + unsigned char prec = xact->amount.commodity().precision(); + + if (! def_commodity) { + def_commodity = commodity_t::find_or_create("$"); + assert(def_commodity); + } + xact->amount.set_commodity(*def_commodity); + + def_commodity->add_flags(flags); + if (prec > def_commodity->precision()) + def_commodity->set_precision(prec); + + if (c == '$') { + saw_splits = true; + xact->amount.in_place_negate(); + } else { + total = xact; + } + break; + } + + case 'C': + SET_BEG_POS_AND_LINE(); + c = in.peek(); + if (c == '*' || c == 'X') { + in.get(c); + xact->state = transaction_t::CLEARED; + } + break; + + case 'N': + SET_BEG_POS_AND_LINE(); + get_line(in); + entry->code = line; + break; + + case 'P': + case 'M': + case 'L': + case 'S': + case 'E': { + SET_BEG_POS_AND_LINE(); + get_line(in); + + switch (c) { + case 'P': + entry->payee = line; + break; + + case 'S': + xact = new transaction_t(NULL); + entry->add_transaction(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] == '[' ? + line + 1 : line); + if (c == 'L') + saw_category = true; + break; + } + + case 'M': + case 'E': + xact->note = line; + break; + } + break; + } + + case 'A': + SET_BEG_POS_AND_LINE(); + // jww (2004-08-19): these are ignored right now + get_line(in); + break; + + case '^': { + account_t * other; + if (xact->account == master) { + if (! misc) + misc = journal->find_account("Miscellaneous"); + other = misc; + } else { + other = master; + } + + if (total && saw_category) { + if (! saw_splits) + total->amount.in_place_negate(); // negate, to show correct flow + else + total->account = other; + } + + if (! saw_splits) { + transaction_t * nxact = new transaction_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); + } + + if (journal->add_entry(entry.get())) { + entry->src_idx = src_idx; + entry->beg_pos = beg_pos; + entry->beg_line = beg_line; + entry->end_pos = in.tellg(); + entry->end_line = linenum; + entry.release(); + count++; + } + + // reset things for the next entry + entry.reset(new entry_t); + xact = new transaction_t(master); + entry->add_transaction(xact); + + saw_splits = false; + saw_category = false; + total = NULL; + beg_line = 0; + break; + } + + default: + get_line(in); + break; + } + } + + return count; +} + +} // namespace ledger diff --git a/src/qif.h b/src/qif.h new file mode 100644 index 00000000..6c68a99b --- /dev/null +++ b/src/qif.h @@ -0,0 +1,21 @@ +#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, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +} // namespace ledger + +#endif // _QIF_H diff --git a/src/quotes.cc b/src/quotes.cc new file mode 100644 index 00000000..1463c9bc --- /dev/null +++ b/src/quotes.cc @@ -0,0 +1,80 @@ +#include "quotes.h" + +namespace ledger { + +void quotes_by_script::operator()(commodity_base_t& commodity, + const ptime& moment, + const ptime& date, + const ptime& last, + amount_t& price) +{ + LOGGER("quotes.download"); + + DEBUG("commodity: " << commodity.symbol); + DEBUG(" now: " << now); + DEBUG(" moment: " << moment); + DEBUG(" date: " << date); + DEBUG(" last: " << last); + + if (SHOW_DEBUG() && commodity.history) + DEBUG("last_lookup: " << commodity.history->last_lookup); + DEBUG("pricing_leeway is " << pricing_leeway); + + if ((commodity.history && + (time_now - commodity.history->last_lookup) < pricing_leeway) || + (time_now - last) < pricing_leeway || + (price && moment > date && (moment - date) <= pricing_leeway)) + return; + + 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(now, price); + + commodity.history->last_lookup = time_now; + cache_dirty = true; + + if (price && ! price_db.empty()) { +#if defined(__GNUG__) && __GNUG__ < 3 + std::ofstream database(price_db.c_str(), ios::out | ios::app); +#else + std::ofstream database(price_db.c_str(), + std::ios_base::out | std::ios_base::app); +#endif +#if 0 + // jww (2007-04-18): Need to convert to local time and print + // here, print with UTC timezone specifier + database << "P " << now.to_string("%Y/%m/%d %H:%M:%S") + << " " << commodity.symbol << " " << price << endl; +#endif + } + } else { + throw exception(string("Failed to download price for '") + + commodity.symbol + "' (command: \"getquote " + + commodity.symbol + "\")", + context()); + } +} + +} // namespace ledger diff --git a/src/quotes.h b/src/quotes.h new file mode 100644 index 00000000..a1fabd93 --- /dev/null +++ b/src/quotes.h @@ -0,0 +1,30 @@ +#ifndef _QUOTES_H +#define _QUOTES_H + +#include "amount.h" + +namespace ledger { + +class quotes_by_script : public commodity_base_t::updater_t +{ + string price_db; + time_duration pricing_leeway; + bool& cache_dirty; + + public: + quotes_by_script(string _price_db, + time_duration _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 ptime& moment, + const ptime& date, + const ptime& last, + amount_t& price); +}; + +} // namespace ledger + +#endif // _QUOTES_H diff --git a/src/reconcile.cc b/src/reconcile.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/reconcile.h b/src/reconcile.h new file mode 100644 index 00000000..e69de29b diff --git a/src/register.cc b/src/register.cc new file mode 100644 index 00000000..9f33bd0c --- /dev/null +++ b/src/register.cc @@ -0,0 +1,166 @@ +#include "register.h" +#include "journal.h" + +namespace ledger { + +string abbreviate(const string& str, + unsigned int width, + elision_style_t elision_style, + const bool is_account, + int abbrev_length) +{ + const unsigned int len = str.length(); + if (len <= width) + return str; + + assert(width < 4095); + + static 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 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::iterator i = parts.begin(); + i != parts.end(); + i++) { + // Don't contract the last element + std::list::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; +} + +static void scan_for_transactions(std::ostream& out, const xml::node_t * node) +{ + if (! (node->flags & XML_NODE_IS_PARENT)) + return; + + const xml::parent_node_t * parent = + static_cast(node); + + for (const xml::node_t * child = parent->children(); + child; + child = child->next) + if (child->name_id == xml::document_t::TRANSACTION) { + const xml::transaction_node_t * xact_node = + dynamic_cast(child); + assert(xact_node); + + const transaction_t * xact = xact_node->transaction; + assert(xact); + + out << xact->entry->date() << ' ' + << std::setw(21) << std::left + << abbreviate(xact->entry->payee, 21) << ' ' + << std::setw(21) << std::left + << abbreviate(xact->account->fullname(), 21, + ABBREVIATE, true) << ' ' + << std::setw(12) << std::right + << xact->amount << '\n'; + } else { + scan_for_transactions(out, child); + } +} + +void register_command::print_document(std::ostream& out, + xml::document_t * doc) +{ +#if 1 + scan_for_transactions(out, doc->top); + out.flush(); +#else + value_t nodelist; + xml::xpath_t::eval(nodelist, "//transaction", doc); + + const value_t::sequence_t * xact_list = nodelist.to_sequence(); + assert(xact_list); + + for (value_t::sequence_t::const_iterator i = xact_list->begin(); + i != xact_list->end(); + i++) { + const xml::node_t * node = (*i).to_xml_node(); + assert(node); + + const xml::transaction_node_t * xact_node = + dynamic_cast(node); + assert(xact_node); + + const transaction_t * xact = xact_node->transaction; + assert(xact); + + std::cout << xact->entry->date() << ' ' + << std::setw(21) << std::left + << abbreviate(xact->entry->payee, 21) << ' ' + << std::setw(21) << std::left + << abbreviate(xact->account->fullname(), 21, + ABBREVIATE, true) << ' ' + << std::setw(12) << std::right + << xact->amount + << std::endl; + } +#endif +} + +} // namespace ledger diff --git a/src/register.h b/src/register.h new file mode 100644 index 00000000..ba2020ec --- /dev/null +++ b/src/register.h @@ -0,0 +1,38 @@ +#ifndef _REGISTER_H +#define _REGISTER_H + +#include "xpath.h" + +namespace ledger { + +class register_command : public xml::xpath_t::functor_t +{ + public: + register_command() : xml::xpath_t::functor_t("register") {} + + virtual void operator()(value_t&, xml::xpath_t::scope_t * locals) { + std::ostream * out = get_ptr(locals, 0); + xml::document_t * doc = get_ptr(locals, 1); + + print_document(*out, doc); + } + + virtual void print_document(std::ostream& out, xml::document_t * doc); +}; + +enum elision_style_t { + TRUNCATE_TRAILING, + TRUNCATE_MIDDLE, + TRUNCATE_LEADING, + ABBREVIATE +}; + +string abbreviate(const string& str, + unsigned int width, + elision_style_t elision_style = TRUNCATE_TRAILING, + const bool is_account = false, + int abbrev_length = 2); + +} // namespace ledger + +#endif // _REGISTER_H diff --git a/src/report.cc b/src/report.cc new file mode 100644 index 00000000..116747ef --- /dev/null +++ b/src/report.cc @@ -0,0 +1,189 @@ +#include "report.h" + +namespace ledger { + +report_t::~report_t() +{ + TRACE_DTOR(report_t); + for (std::list::const_iterator i = transforms.begin(); + i != transforms.end(); + i++) + delete *i; +} + +void report_t::apply_transforms(xml::document_t * document) +{ + for (std::list::const_iterator i = transforms.begin(); + i != transforms.end(); + i++) + (*i)->execute(document); +} + +void report_t::abbrev(value_t& result, xml::xpath_t::scope_t * locals) +{ + if (locals->args.size() < 2) + throw_(exception, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); + + string str = locals->args[0].to_string(); + long wid = locals->args[1]; + + elision_style_t style = session->elision_style; + if (locals->args.size() == 3) + style = (elision_style_t)locals->args[2].to_integer(); + + long abbrev_len = session->abbrev_length; + if (locals->args.size() == 4) + abbrev_len = locals->args[3].to_integer(); + + result.set_string(abbreviate(str, wid, style, true, (int)abbrev_len)); +} + +void report_t::ftime(value_t&, xml::xpath_t::scope_t * locals) +{ + if (locals->args.size() < 1) + throw_(exception, "usage: ftime(DATE [, DATE_FORMAT])"); + + moment_t date = locals->args[0].to_datetime(); + + string date_format; + if (locals->args.size() == 2) + date_format = locals->args[1].to_string(); +#if 0 + // jww (2007-04-18): Need to setup an output facet here + else + date_format = moment_t::output_format; + + result.set_string(date.to_string(date_format)); +#endif +} + +bool report_t::resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'a': + if (name == "abbrev") { + abbrev(result, locals); + return true; + } + break; + + case 'f': + if (name == "ftime") { + ftime(result, locals); + return true; + } + break; + } + + return xml::xpath_t::scope_t::resolve(name, result, locals); +} + +xml::xpath_t::op_t * report_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'a': +#if 0 + if (std::strcmp(p, "accounts") == 0) + return MAKE_FUNCTOR(report_t, option_accounts); + else +#endif + if (std::strcmp(p, "amount") == 0) + return MAKE_FUNCTOR(report_t, option_amount); + break; + + case 'b': + if (std::strcmp(p, "bar") == 0) + return MAKE_FUNCTOR(report_t, option_bar); + break; + +#if 0 + case 'c': + if (std::strcmp(p, "clean") == 0) + return MAKE_FUNCTOR(report_t, option_clean); + else if (std::strcmp(p, "compact") == 0) + return MAKE_FUNCTOR(report_t, option_compact); + break; +#endif + + case 'e': +#if 0 + if (std::strcmp(p, "entries") == 0) + return MAKE_FUNCTOR(report_t, option_entries); + else if (std::strcmp(p, "eval") == 0) + return MAKE_FUNCTOR(report_t, option_eval); + else if (std::strcmp(p, "exclude") == 0) + return MAKE_FUNCTOR(report_t, option_remove); +#endif + break; + + case 'f': + if (std::strcmp(p, "foo") == 0) + return MAKE_FUNCTOR(report_t, option_foo); + else if (std::strcmp(p, "format") == 0) + return MAKE_FUNCTOR(report_t, option_format); + break; + + case 'i': +#if 0 + if (std::strcmp(p, "include") == 0) + return MAKE_FUNCTOR(report_t, option_select); +#endif + break; + + case 'l': +#if 0 + if (! *(p + 1) || std::strcmp(p, "limit") == 0) + return MAKE_FUNCTOR(report_t, option_limit); +#endif + break; + +#if 0 + case 'm': + if (std::strcmp(p, "merge") == 0) + return MAKE_FUNCTOR(report_t, option_merge); + break; +#endif + + case 'r': +#if 0 + if (std::strcmp(p, "remove") == 0) + return MAKE_FUNCTOR(report_t, option_remove); +#endif + break; + +#if 0 + case 's': + if (std::strcmp(p, "select") == 0) + return MAKE_FUNCTOR(report_t, option_select); + else if (std::strcmp(p, "split") == 0) + return MAKE_FUNCTOR(report_t, option_split); + break; +#endif + + case 't': + if (! *(p + 1)) + 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 (! *(p + 1)) + return MAKE_FUNCTOR(report_t, option_total); + break; + } + } + break; + } + + return xml::xpath_t::scope_t::lookup(name); +} + +} // namespace ledger diff --git a/src/report.h b/src/report.h new file mode 100644 index 00000000..11a0b759 --- /dev/null +++ b/src/report.h @@ -0,0 +1,141 @@ +#ifndef _REPORT_H +#define _REPORT_H + +#include "session.h" +#include "transform.h" + +namespace ledger { + +typedef std::list strings_list; + +class report_t : public xml::xpath_t::scope_t +{ + public: + string output_file; + string format_string; + string amount_expr; + string total_expr; + string date_output_format; + + unsigned long budget_flags; + + string account; + string pager; + + bool show_totals; + bool raw_mode; + + session_t * session; + transform_t * last_transform; + + std::list transforms; + + report_t(session_t * _session) + : xml::xpath_t::scope_t(_session), + show_totals(false), + raw_mode(false), + session(_session), + last_transform(NULL) + { + TRACE_CTOR(report_t, "session_t *"); + eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); + } + + virtual ~report_t(); + + void apply_transforms(xml::document_t * document); + + // + // Utility functions for value expressions + // + + void ftime(value_t& result, xml::xpath_t::scope_t * locals); + void abbrev(value_t& result, xml::xpath_t::scope_t * locals); + + // + // Config options + // + + void eval(const string& expr) { + xml::xpath_t(expr).compile((xml::document_t *)NULL, this); + } + void option_eval(value_t&, xml::xpath_t::scope_t * locals) { + eval(locals->args[0].to_string()); + } + + void option_amount(value_t&, xml::xpath_t::scope_t * locals) { + eval(string("t=") + locals->args[0].to_string()); + } + void option_total(value_t&, xml::xpath_t::scope_t * locals) { + eval(string("T()=") + locals->args[0].to_string()); + } + + void option_format(value_t&, xml::xpath_t::scope_t * locals) { + format_string = locals->args[0].to_string(); + } + + void option_raw(value_t&) { + raw_mode = true; + } + + void option_foo(value_t&) { + std::cout << "This is foo" << std::endl; + } + void option_bar(value_t&, xml::xpath_t::scope_t * locals) { + std::cout << "This is bar: " << locals->args[0] << std::endl; + } + + // + // Transform options + // + +#if 0 + void option_select(value_t&, xml::xpath_t::scope_t * locals) { + transforms.push_back(new select_transform(locals->args[0].to_string())); + } + void option_limit(value_t&, xml::xpath_t::scope_t * locals) { + string expr = (string("//xact[") + + locals->args[0].to_string() + "]"); + transforms.push_back(new select_transform(expr)); + } + + void option_remove(value_t&, xml::xpath_t::scope_t * locals) { + transforms.push_back(new remove_transform(locals->args[0].to_string())); + } + + void option_accounts(value_t&) { + transforms.push_back(new accounts_transform); + } + void option_compact(value_t&) { + transforms.push_back(new compact_transform); + } + void option_clean(value_t&) { + transforms.push_back(new clean_transform); + } + void option_entries(value_t&) { + transforms.push_back(new entries_transform); + } + + void option_split(value_t&) { + transforms.push_back(new split_transform); + } + void option_merge(value_t&) { + transforms.push_back(new merge_transform); + } +#endif + + // + // Scope members + // + + virtual bool resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals); + virtual xml::xpath_t::op_t * lookup(const string& name); +}; + +string abbrev(const string& str, unsigned int width, + const bool is_account); + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/session.cc b/src/session.cc new file mode 100644 index 00000000..78fc2596 --- /dev/null +++ b/src/session.cc @@ -0,0 +1,220 @@ +#include "session.h" + +namespace ledger { + +unsigned int session_t::read_journal(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ + if (! master) + master = journal->master; + + for (std::list::iterator i = parsers.begin(); + i != parsers.end(); + i++) + if ((*i)->test(in)) + return (*i)->parse(in, journal, master, original_file); + + return 0; +} + +unsigned int session_t::read_journal(const string& path, + journal_t * journal, + account_t * master, + const string * original_file) +{ + journal->sources.push_back(path); + + if (access(path.c_str(), R_OK) == -1) + throw_(exception, "Cannot read file '" << path << "'"); + + if (! original_file) + original_file = &path; + + std::ifstream stream(path.c_str()); + return read_journal(stream, journal, master, original_file); +} + +void session_t::read_init() +{ + if (init_file.empty()) + return; + + if (access(init_file.c_str(), R_OK) == -1) + throw_(exception, "Cannot read init file '" << init_file << "'"); + + std::ifstream init(init_file.c_str()); + + // jww (2006-09-15): Read initialization options here! +} + +journal_t * session_t::read_data(const string& master_account) +{ + TRACE_START(parser, 1, "Parsing journal file"); + + journal_t * journal = new_journal(); + journal->document = new xml::document_t; + journal->document->set_top(xml::wrap_node(journal->document, journal)); + + unsigned int entry_count = 0; + + DEBUG_("ledger.cache", "3. use_cache = " << use_cache); + + if (use_cache && ! cache_file.empty() && + ! data_file.empty()) { + DEBUG_("ledger.cache", "using_cache " << cache_file); + cache_dirty = true; + if (access(cache_file.c_str(), R_OK) != -1) { + std::ifstream stream(cache_file.c_str()); + + string price_db_orig = journal->price_db; + journal->price_db = price_db; + entry_count += read_journal(stream, journal, NULL, + &data_file); + if (entry_count > 0) + cache_dirty = false; + else + journal->price_db = price_db_orig; + } + } + + if (entry_count == 0 && ! data_file.empty()) { + account_t * acct = NULL; + if (! master_account.empty()) + acct = journal->find_account(master_account); + + journal->price_db = price_db; + if (! journal->price_db.empty() && + access(journal->price_db.c_str(), R_OK) != -1) { + if (read_journal(journal->price_db, journal)) { + throw_(exception, "Entries not allowed in price history file"); + } else { + DEBUG_("ledger.cache", "read price database " << journal->price_db); + journal->sources.pop_back(); + } + } + + DEBUG_("ledger.cache", "rejected cache, parsing " << data_file); + if (data_file == "-") { + use_cache = false; + journal->sources.push_back(""); + entry_count += read_journal(std::cin, journal, acct); + } + else if (access(data_file.c_str(), R_OK) != -1) { + entry_count += read_journal(data_file, journal, acct); + if (! journal->price_db.empty()) + journal->sources.push_back(journal->price_db); + } + } + + VERIFY(journal->valid()); + + if (entry_count == 0) + throw_(exception, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); + + TRACE_STOP(parser, 1); + + return journal; +} + +bool session_t::resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'd': + if (name == "date_format") { + // jww (2007-04-18): What to do here? +#if 0 + result.set_string(moment_t::output_format); +#endif + return true; + } + break; + + case 'n': + switch (*++p) { + case 'o': + if (name == "now") { + result = now; + return true; + } + break; + } + break; + + case 'r': + if (name == "register_format") { + result = register_format; + return true; + } + break; + } + + return xml::xpath_t::scope_t::resolve(name, result, locals); +} + +xml::xpath_t::op_t * session_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'd': + if (std::strcmp(p, "debug") == 0) + return MAKE_FUNCTOR(session_t, option_debug); + break; + + case 'f': + if (! *(p + 1) || 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, "verify") == 0) + return MAKE_FUNCTOR(session_t, option_verify); + break; + } + } + break; + } + + return xml::xpath_t::scope_t::lookup(name); +} + +// jww (2007-04-26): All of Ledger should be accessed through a +// session_t object +void initialize() +{ + IF_VERIFY() + initialize_memory_tracing(); + + amount_t::initialize(); + xml::xpath_t::initialize(); +} + +void shutdown() +{ + xml::xpath_t::shutdown(); + amount_t::shutdown(); + + IF_VERIFY() { + INFO("Ledger shutdown (Boost/libstdc++ may still hold memory)"); + shutdown_memory_tracing(); + } else { + INFO("Ledger shutdown"); + } +} + +} // namespace ledger diff --git a/src/session.h b/src/session.h new file mode 100644 index 00000000..a1fd304b --- /dev/null +++ b/src/session.h @@ -0,0 +1,197 @@ +#ifndef _SESSION_H +#define _SESSION_H + +#include "journal.h" +#include "parser.h" +#include "register.h" + +namespace ledger { + +class session_t : public xml::xpath_t::scope_t +{ + public: + string init_file; + string data_file; + string cache_file; + string 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; + + moment_t now; + + elision_style_t elision_style; + + int abbrev_length; + + bool ansi_codes; + bool ansi_invert; + + std::list journals; + std::list parsers; + + session_t(xml::xpath_t::scope_t * _parent = NULL) : + xml::xpath_t::scope_t(_parent), + + register_format + ("%((//entry)%{date} %-.20{payee}" + "%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"), + wide_register_format + ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" + "%48|%-.38A %22.108t %!22.132T\n"), + print_format +#if 1 + ("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"), +#else + ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), +#endif + balance_format + ("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"), + equity_format + + ("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\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), + + elision_style(ABBREVIATE), + abbrev_length(2), + + ansi_codes(false), + ansi_invert(false) { + TRACE_CTOR(session_t, "xml::xpath_t::scope_t *"); + } + + virtual ~session_t() { + TRACE_DTOR(session_t); + + for (std::list::iterator i = journals.begin(); + i != journals.end(); + i++) + delete *i; + + for (std::list::iterator i = parsers.begin(); + i != parsers.end(); + i++) + delete *i; + } + + journal_t * new_journal() { + journal_t * journal = new journal_t(this); + journals.push_back(journal); + return journal; + } + void close_journal(journal_t * journal) { + journals.remove(journal); + delete journal; + } + + unsigned int read_journal(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); + + unsigned int read_journal(const string& path, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); + + void read_init(); + + journal_t * read_data(const string& master_account = ""); + + void register_parser(parser_t * parser) { + parsers.push_back(parser); + } + bool unregister_parser(parser_t * parser) { + std::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; + } + + // + // Scope members + // + + virtual bool resolve(const string& name, value_t& result, + xml::xpath_t::scope_t * locals = NULL); + virtual xml::xpath_t::op_t * lookup(const string& name); + + // + // Debug options + // + + void option_verify(value_t&) {} + void option_trace(value_t&, xml::xpath_t::scope_t * locals) {} + void option_debug(value_t&, xml::xpath_t::scope_t * locals) {} + + void option_verbose(value_t&) { + if (_log_level < LOG_INFO) + _log_level = LOG_INFO; + } + + // + // Option handlers + // + + void option_file(value_t&, xml::xpath_t::scope_t * locals) { + data_file = locals->args.to_string(); + } + +#if 0 +#if defined(USE_BOOST_PYTHON) + void option_import(value_t&) { + python_import(optarg); + } + void option_import_stdin(value_t&) { + python_eval(std::cin, PY_EVAL_MULTI); + } +#endif +#endif +}; + +void initialize(); +void shutdown(); + +} // namespace ledger + +#endif // _SESSION_H diff --git a/src/system.hh b/src/system.hh new file mode 100644 index 00000000..bf376deb --- /dev/null +++ b/src/system.hh @@ -0,0 +1,99 @@ +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + } +} +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if defined __FreeBSD__ && __FreeBSD__ <= 4 +// FreeBSD has a broken isspace macro, so don't use it +#undef isspace(c) +#endif + +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) +#include +#endif + +#if defined(HAVE_NL_LANGINFO) +#include +#endif + +#include + +#define HAVE_GDTOA 1 +#ifdef HAVE_GDTOA +#include +#endif + +extern "C" { +#if defined(HAVE_EXPAT) +#include // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include // expat XML parser +#endif +} + +#if defined(HAVE_LIBOFX) +#include +#endif + +#endif // _SYSTEM_HH diff --git a/src/textual.cc b/src/textual.cc new file mode 100644 index 00000000..ab657898 --- /dev/null +++ b/src/textual.cc @@ -0,0 +1,966 @@ +#include "textual.h" +#include "session.h" + +#define TIMELOG_SUPPORT 1 + +namespace ledger { + +#define MAX_LINE 1024 + +static string path; +static unsigned int linenum; +static unsigned int src_idx; +static accounts_map account_aliases; + +static std::list > include_stack; + +#ifdef TIMELOG_SUPPORT +struct time_entry_t { + moment_t checkin; + account_t * account; + string desc; +}; +std::list time_entries; +#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 inline void +parse_amount_expr(std::istream& in, journal_t *, + transaction_t& xact, amount_t& amount, + unsigned short flags = 0) +{ + xml::xpath_t xpath(in, flags | XPATH_PARSE_RELAXED | XPATH_PARSE_PARTIAL); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed an amount expression"); + +#if 0 + IF_DEBUG_("ledger.textual.parse") { + if (_debug_stream) { + xpath.dump(*_debug_stream); + *_debug_stream << std::endl; + } + } +#endif + + amount = xpath.calc(static_cast(xact.data)).to_amount(); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "The transaction amount is " << amount); +} + +transaction_t * parse_transaction(char * line, + journal_t * journal, + account_t * account, + entry_t * entry = NULL) +{ + // The account will be determined later... + std::auto_ptr xact(new transaction_t(NULL)); + + // First cut up the input line into its various parts. + + char * state = NULL; + char * account_path = NULL; + char * amount = NULL; + char * note = NULL; + + char * p = line; + + if (*p == '*' || *p == '!') + state = p++; + + account_path = skip_ws(p); + + amount = next_element(account_path, true); + if (amount) { + char * p = amount; + while (*p && *p != ';') + p++; + + if (*p == ';') { + *p++ = '\0'; + note = skip_ws(p); + } + + p = amount + (std::strlen(amount) - 1); + while (p > amount && std::isspace(*p)) + p--; + + if (std::isspace(*(p + 1))) + *++p = '\0'; + } + + string err_desc; +#if 0 + try { +#endif + + xact->entry = entry; // this might be NULL + + // Parse the state flag + + if (state) + switch (*state) { + case '*': + xact->state = transaction_t::CLEARED; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the CLEARED flag"); + break; + case '!': + xact->state = transaction_t::PENDING; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed the PENDING flag"); + break; + } + + // Parse the account name + + char * b = &account_path[0]; + char * e = &account_path[std::strlen(account_path) - 1]; + if ((*b == '[' && *e == ']') || + (*b == '(' && *e == ')')) { + xact->flags |= TRANSACTION_VIRTUAL; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a virtual account name"); + if (*b == '[') { + xact->flags |= TRANSACTION_BALANCE; + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed a balanced virtual account name"); + } + *account_path++ = '\0'; + *e = '\0'; + } + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Parsed account name " << account_path); + if (account_aliases.size() > 0) { + accounts_map::const_iterator i = account_aliases.find(account_path); + if (i != account_aliases.end()) + xact->account = (*i).second; + } + if (! xact->account) + xact->account = account->find_account(account_path); + + // Parse the optional amount + + if (amount && *amount) { + std::istringstream in(amount); + + try { + // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol + + unsigned long beg = (long)in.tellg(); + + xact->amount.parse(in, AMOUNT_PARSE_NO_REDUCE); + + char c; + if (! in.eof() && (c = peek_next_nonws(in)) != '@' && + c != ';' && ! in.eof()) { + in.seekg(beg, std::ios::beg); + + if (xact->entry) { + // Create a report item for this entry, so the transaction + // below may refer to it + + if (! xact->entry->data) + xact->entry->data = xml::wrap_node(journal->document, xact->entry, + journal->document->top); + + xact->data = xml::wrap_node(journal->document, xact.get(), + xact->entry->data); + } + + parse_amount_expr(in, journal, *xact, xact->amount, + XPATH_PARSE_NO_REDUCE); + + if (xact->entry) { + delete static_cast(xact->data); + xact->data = NULL; + } + + unsigned long end = (long)in.tellg(); + + xact->amount_expr = string(line, beg, end - beg); + } + } + catch (exception& err) { + err_desc = "While parsing transaction amount:"; + throw; + } + + // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) + + if (in.good() && ! in.eof()) { + char c = peek_next_nonws(in); + if (c == '@') { + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Found a price indicator"); + bool per_unit = true; + in.get(c); + if (in.peek() == '@') { + in.get(c); + per_unit = false; + DEBUG_("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(); + + xact->cost->parse(in); + + unsigned long end = (long)in.tellg(); + + if (per_unit) + xact->cost_expr = (string("@") + + string(amount, beg, end - beg)); + else + xact->cost_expr = (string("@@") + + string(amount, beg, end - beg)); + } + catch (exception& err) { + err_desc = "While parsing transaction cost:"; + throw; + } + + if (*xact->cost < 0) + throw_(parse_exception, "A transaction's cost may not be negative"); + + amount_t per_unit_cost(*xact->cost); + if (per_unit) + *xact->cost *= xact->amount.number(); + else + per_unit_cost /= xact->amount.number(); + + if (xact->amount.commodity() && + ! xact->amount.commodity().annotated) + xact->amount.annotate_commodity(per_unit_cost, + xact->entry->actual_date(), + xact->entry->code); + + 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); + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Bare amount is " << xact->amount.number()); + } + } + } + + xact->amount.in_place_reduce(); + + DEBUG_("ledger.textual.parse", "line " << linenum << ": " << + "Reduced amount is " << xact->amount); + } + + // Parse the optional note + + if (note) { + xact->note = note; + 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_datetime(p); + } + if (buf[0]) + xact->_date = parse_datetime(buf); + } + } + + return xact.release(); + +#if 0 + } + catch (error * err) { + err->context.push_back + (new line_context(line, -1, ! err_desc.empty() ? + err_desc : "While parsing transaction:")); + throw err; + } +#endif +} + +bool parse_transactions(std::istream& in, + journal_t * journal, + account_t * account, + entry_base_t& entry, + const 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; + + beg_pos += std::strlen(line) + 1; + linenum++; + + char * p = skip_ws(line); + if (! *p || *p == '\r' || *p == '\n') + break; + + if (transaction_t * xact = parse_transaction(p, journal, account)) { + entry.add_transaction(xact); + added = true; + } + } + + return added; +} + +entry_t * parse_entry(std::istream& in, char * line, journal_t * journal, + account_t * master, textual_parser_t& /* parser */, + unsigned long beg_pos) +{ + TRACE_START(entry_text, 1, "Time spent preparing entry text:"); + + std::auto_ptr curr(new entry_t); + + // First cut up the input line into its various parts. + + char * date = NULL; + char * date_eff = NULL; + char * statep = NULL; + char * code = NULL; + char * payee = NULL; + + date = line; + + char * p = line; + + while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) + p++; + assert(*p); + + if (*p == '=') { + *p++ = '\0'; + date_eff = p; + + while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) + p++; + assert(*p); + } else { + *p++ = '\0'; + } + + p = skip_ws(p); + + if (*p == '*' || *p == '!') { + statep = p; + p++; *p++ = '\0'; + + p = skip_ws(p); + } + + if (*p == '(') { + code = ++p; + while (*p && *p != ')') + p++; + assert(*p); + *p++ = '\0'; + + p = skip_ws(p); + } + + payee = p; + + p = payee + (std::strlen(payee) - 1); + while (p > payee && std::isspace(*p)) + p--; + + if (std::isspace(*(p + 1))) + *++p = '\0'; + + TRACE_STOP(entry_text, 1); + + // Parse the date + + TRACE_START(entry_date, 1, "Time spent parsing entry dates:"); + + curr->_date = parse_datetime(date); + + if (date_eff) + curr->_date_eff = parse_datetime(date_eff); + + TRACE_STOP(entry_date, 1); + + // Parse the optional cleared flag: * + + TRACE_START(entry_details, 1, "Time spent parsing entry details:"); + + transaction_t::state_t state = transaction_t::UNCLEARED; + if (statep) { + switch (*statep) { + case '*': + state = transaction_t::CLEARED; + break; + case '!': + state = transaction_t::PENDING; + break; + } + } + + // Parse the optional code: (TEXT) + + if (code) + curr->code = code; + + // Parse the payee/description text + + assert(payee); + curr->payee = *payee != '\0' ? payee : ""; + + TRACE_STOP(entry_details, 1); + + // Parse all of the transactions associated with this entry + + TRACE_START(entry_xacts, 1, "Time spent parsing transactions:"); + + unsigned long end_pos; + unsigned long beg_line = linenum; + + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { + line[0] = '\0'; + in.getline(line, MAX_LINE); + if (in.eof() || line[0] == '\0') + break; + end_pos = beg_pos + std::strlen(line) + 1; + linenum++; + + char * p = skip_ws(line); + if (! *p || *p == '\r' || *p == '\n') + break; + + if (transaction_t * xact = parse_transaction(p, journal, 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; + beg_pos = end_pos; + + curr->add_transaction(xact); + } + + if (in.eof()) + break; + } + + if (curr->data) { + delete static_cast(curr->data); + curr->data = NULL; + } + + TRACE_STOP(entry_xacts, 1); + + return curr.release(); +} + +template +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, string& symbol) +{ + if (*p == '"') { + char * q = std::strchr(p + 1, '"'); + if (! q) + throw_(parse_exception, "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_exception, "Failed to parse commodity"); +} + +bool textual_parser_t::test(std::istream& in) const +{ + char buf[5]; + + in.read(buf, 5); + if (std::strncmp(buf, "::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_exception, + "Timelog check-out event does not match any current check-ins"); + } + + if (desc && event.desc.empty()) { + event.desc = desc; + desc = NULL; + } + + std::auto_ptr curr(new entry_t); + curr->_date = when; + curr->code = desc ? desc : ""; + curr->payee = event.desc; + + if (curr->_date < event.checkin) + throw_(parse_exception, + "Timelog check-out date less than corresponding check-in"); + + char buf[32]; + std::sprintf(buf, "%lds", (long)(curr->_date - event.checkin).total_seconds()); + 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_(parse_exception, "Failed to record 'out' timelog entry"); + else + curr.release(); +} + +unsigned int textual_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const string * original_file) +{ + static bool added_auto_entry_hook = false; + static char line[MAX_LINE + 1]; + unsigned int count = 0; + unsigned int errors = 0; + + TRACE_START(parsing_total, 1, "Total time spent parsing text:"); + + std::list account_stack; + + auto_entry_finalizer_t auto_entry_finalizer(journal); + + if (! master && journal) + master = journal->master; + + account_stack.push_front(master); + + path = journal ? journal->sources.back() : *original_file; + src_idx = journal ? journal->sources.size() - 1 : 0; + linenum = 1; + + INFO("Parsing file '" << path << "'"); + + unsigned long beg_pos = in.tellg(); + unsigned long end_pos; + unsigned long beg_line = linenum; + + while (in.good() && ! in.eof()) { +#if 0 + try { +#endif + in.getline(line, MAX_LINE); + if (in.eof()) + break; + end_pos = beg_pos + std::strlen(line) + 1; + linenum++; + + switch (line[0]) { + case '\0': + case '\r': + break; + + case ' ': + case '\t': { + char * p = skip_ws(line); + if (*p && *p != '\r') + throw_(parse_exception, "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; + event.desc = n ? n : ""; + event.checkin = parse_datetime(date); + event.account = account_stack.front()->find_account(p); + + if (! time_entries.empty()) + for (std::list::iterator i = time_entries.begin(); + i != time_entries.end(); + i++) + if (event.account == (*i).account) + throw_(parse_exception, + "Cannot double check-in to the same account"); + + time_entries.push_back(event); + break; + } + + case 'o': + case 'O': + if (time_entries.empty()) { + throw_(parse_exception, "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 + (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)); + 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; + string date_field = date_field_ptr; + + char * symbol_and_price; + moment_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); + + 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); + 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 current year +#if 0 + // jww (2007-04-18): Need to set this up again + date_t::current_year = std::atoi(skip_ws(line + 1)); +#endif + break; + +#ifdef TIMELOG_SUPPORT + case 'h': + case 'b': +#endif + case ';': // comment + break; + + case '-': // option setting + throw_(parse_exception, "Option settings are not allowed in journal files"); + + 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, journal, 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_exception, string("Parsing time period '") + skip_ws(line + 1) + "'"); + + if (parse_transactions(in, journal, 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_(parse_exception, "Period entry failed to balance"); + } + } + break; + } + + case '@': + case '!': { // directive + char * p = next_element(line); + string word(line + 1); + if (word == "include") { + push_var save_path(path); + push_var save_src_idx(src_idx); + push_var save_beg_pos(beg_pos); + push_var save_end_pos(end_pos); + push_var save_linenum(linenum); + + path = p; + if (path[0] != '/' && path[0] != '\\' && path[0] != '~') { + string::size_type pos = save_path.prev.rfind('/'); + if (pos == string::npos) + pos = save_path.prev.rfind('\\'); + if (pos != string::npos) + path = string(save_path.prev, 0, pos + 1) + path; + } + path = resolve_path(path); + + DEBUG_("ledger.textual.include", "line " << linenum << ": " << + "Including path '" << path << "'"); + + include_stack.push_back(std::pair + (journal->sources.back(), linenum - 1)); + count += journal->session->read_journal(path, 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 result + = account_aliases.insert(accounts_pair(b, acct)); + assert(result.second); + } + } + else if (word == "def" || word == "eval") { + // jww (2006-09-13): Read the string after and evaluate it. + // But also keep a list of these value expressions, and a + // way to know where they fall in the transaction sequence. + // This will be necessary so that binary file reading can + // re-evaluate them at the appopriate time. + + // compile(&journal->defs); + } + break; + } + + default: { + //unsigned int first_line = linenum; + unsigned long pos = end_pos; + + TRACE_START(entries, 1, "Time spent handling entries:"); + if (entry_t * entry = parse_entry(in, line, journal, + 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 = end_pos; + entry->end_line = linenum; + count++; + } else { + delete entry; + throw_(parse_exception, "Entry does not balance"); + } + } else { + throw_(parse_exception, "Failed to parse entry"); + } + TRACE_STOP(entries, 1); + + end_pos = pos; + break; + } + } +#if 0 + } + catch (error * err) { + for (std::list >::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++; + } +#endif + beg_pos = end_pos; + } + + if (! time_entries.empty()) { + for (std::list::iterator i = time_entries.begin(); + i != time_entries.end(); + i++) + clock_out_from_timelog(now, (*i).account, NULL, journal); + time_entries.clear(); + } + + if (added_auto_entry_hook) + journal->remove_entry_finalizer(&auto_entry_finalizer); + + if (errors > 0) + throw (int)errors; + + TRACE_STOP(parsing_total, 1); + + return count; +} + +} // namespace ledger diff --git a/src/textual.h b/src/textual.h new file mode 100644 index 00000000..bf05b1fc --- /dev/null +++ b/src/textual.h @@ -0,0 +1,44 @@ +#ifndef _TEXTUAL_H +#define _TEXTUAL_H + +#include "parser.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, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +#if 0 +void write_textual_journal(journal_t& journal, string path, + item_handler& formatter, + const string& write_hdr_format, + std::ostream& out); +#endif + +#if 0 +class include_context : public file_context { + public: + include_context(const string& 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 << "\", 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..8966d598 --- /dev/null +++ b/src/times.cc @@ -0,0 +1,53 @@ +#include "times.h" + +namespace ledger { + +#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(); + +#ifdef SUPPORT_DATE_AND_TIME +const moment_t& now(time_now); +#else +const moment_t& now(date_now); +#endif + +bool day_before_month = false; +static bool day_before_month_initialized = false; + +moment_t parse_datetime(const char * str) +{ + if (! day_before_month_initialized) { +#ifdef HAVE_NL_LANGINFO + const char * d_fmt = nl_langinfo(D_FMT); + if (d_fmt && std::strlen(d_fmt) > 1 && d_fmt[1] == 'd') + day_before_month = true; + day_before_month_initialized = true; +#endif + } +#if 0 + return parse_abs_datetime(in); +#else + int year = ((str[0] - '0') * 1000 + + (str[1] - '0') * 100 + + (str[2] - '0') * 10 + + (str[3] - '0')); + + int mon = ((str[5] - '0') * 10 + + (str[6] - '0')); + + int day = ((str[8] - '0') * 10 + + (str[9] - '0')); + + return moment_t(boost::gregorian::date(year, mon, day)); +#endif +} + +moment_t datetime_range_from_stream(std::istream& in) +{ +} + +} // namespace ledger diff --git a/src/times.h b/src/times.h new file mode 100644 index 00000000..2cc0d7e4 --- /dev/null +++ b/src/times.h @@ -0,0 +1,102 @@ +#ifndef _TIMES_H +#define _TIMES_H + +#include "utils.h" + +#include + +namespace ledger { + +typedef boost::posix_time::ptime ptime; +typedef ptime::time_duration_type time_duration; +typedef boost::gregorian::date date; +typedef boost::gregorian::date_duration date_duration; +typedef boost::posix_time::seconds seconds; + +#define SUPPORT_DATE_AND_TIME 1 +#ifdef SUPPORT_DATE_AND_TIME + +typedef boost::posix_time::ptime moment_t; +typedef moment_t::time_duration_type duration_t; + +inline bool is_valid_moment(const moment_t& moment) { + return ! moment.is_not_a_date_time(); +} + +#else // SUPPORT_DATE_AND_TIME + +typedef boost::gregorian::date moment_t; +typedef boost::gregorian::date_duration duration_t; + +inline bool is_valid_moment(const moment_t& moment) { + return ! moment.is_not_a_date(); +} + +#endif // SUPPORT_DATE_AND_TIME + +extern const moment_t& now; + +DECLARE_EXCEPTION(datetime_exception); + +class interval_t +{ +public: + interval_t() {} + interval_t(const string&) {} + + operator bool() const { + return false; + } + + void start(const moment_t&) {} + moment_t next() const { return moment_t(); } + + void parse(std::istream&) {} +}; + +#if 0 +inline moment_t ptime_local_to_utc(const moment_t& when) { + struct std::tm tm_gmt = to_tm(when); + return boost::posix_time::from_time_t(std::mktime(&tm_gmt)); +} + +// jww (2007-04-18): I need to make a general parsing function +// instead, and then make these into private methods. +inline moment_t ptime_from_local_date_string(const string& date_string) { + return ptime_local_to_utc(moment_t(boost::gregorian::from_string(date_string), + time_duration())); +} + +inline moment_t ptime_from_local_time_string(const string& time_string) { + return ptime_local_to_utc(boost::posix_time::time_from_string(time_string)); +} +#endif + +moment_t parse_datetime(const char * str); + +inline moment_t parse_datetime(const string& str) { + return parse_datetime(str.c_str()); +} + +extern const ptime time_now; +extern const date date_now; +extern bool day_before_month; + +#if 0 +struct intorchar +{ + int ival; + string sval; + + intorchar() : ival(-1) {} + intorchar(int val) : ival(val) {} + intorchar(const string& val) : ival(-1), sval(val) {} + intorchar(const intorchar& o) : ival(o.ival), sval(o.sval) {} +}; + +ledger::moment_t parse_abs_datetime(std::istream& input); +#endif + +} // namespace ledger + +#endif // _TIMES_H diff --git a/src/transform.cc b/src/transform.cc new file mode 100644 index 00000000..b6a25cee --- /dev/null +++ b/src/transform.cc @@ -0,0 +1,326 @@ +#include "transform.h" + +namespace ledger { + +#if 0 +void populate_account(account_t& acct, xml::document_t * document) +{ + if (! acct.parent) + return; + + account_repitem_t * acct_item; + if (acct.data == NULL) { + acct.data = acct_item = + static_cast(repitem_t::wrap(&acct)); + if (acct.parent) { + if (acct.parent->data == NULL) + populate_account(*acct.parent, acct_item); + else + static_cast(acct.parent->data)-> + add_child(acct_item); + } + } else { + acct_item = static_cast(acct.data); + } + + if (item->kind == repitem_t::ACCOUNT) + acct_item->add_child(item); + else + acct_item->add_content(item); +} + +class populate_accounts : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->kind == repitem_t::TRANSACTION) { + item->extract(); + populate_account(*static_cast(item)->account(), item); + } + } +}; + +class clear_account_data : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->kind == repitem_t::ACCOUNT) + static_cast(item)->account->data = NULL; + } +}; + +void accounts_transform::execute(xml::document_t * document) +{ + populate_accounts cb1; + items->select_all(cb1); + + for (repitem_t * j = items->children; j; j = j->next) { + assert(j->kind == repitem_t::JOURNAL); + + j->clear(); + + for (accounts_map::iterator i = j->journal->master->accounts.begin(); + i != j->journal->master->accounts.end(); + i++) { + assert((*i).second->data); + j->add_child(static_cast((*i).second->data)); + (*i).second->data = NULL; + } + } + + clear_account_data cb2; + items->select_all(cb2); +} + +void compact_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->kind == repitem_t::ACCOUNT) { + while (! i->contents && + i->children && ! i->children->next) { + account_repitem_t * p = static_cast(i); + i = p->children; + p->children = NULL; + p->last_child = NULL; + + i->set_parent(p->parent); + p->set_parent(NULL); + i->prev = p->prev; + if (p->prev) + p->prev->next = i; + p->prev = NULL; + i->next = p->next; + if (p->next) + p->next->prev = i; + p->next = NULL; + + if (i->parent->children == p) + i->parent->children = i; + if (i->parent->last_child == p) + i->parent->last_child = i; + + account_repitem_t * acct = static_cast(i); + acct->parents_elided = p->parents_elided + 1; + + delete p; + } + } + + if (i->children) + execute(i->children); + } +} + +void clean_transform::execute(xml::document_t * document) +{ + repitem_t * i = items; + while (i) { + if (i->kind == repitem_t::ACCOUNT) { + value_t temp; + i->add_total(temp); + if (! temp) { + repitem_t * next = i->next; + delete i; + i = next; + continue; + } + } +#if 0 + else if (i->kind == repitem_t::ENTRY && ! i->contents) { + assert(! i->children); + repitem_t * next = i->next; + delete i; + i = next; + continue; + } +#endif + + if (i->children) + execute(i->children); + + i = i->next; + } +} + +void entries_transform::execute(xml::document_t * document) +{ +} + +void optimize_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->kind == repitem_t::ENTRY) { + if (i->contents && + i->contents->next && + ! i->contents->next->next) { // exactly two transactions + xact_repitem_t * first = + static_cast(i->contents); + xact_repitem_t * second = + static_cast(i->contents->next); + if (first->xact->amount == - second->xact->amount) + ; + } + } + + if (i->children) + execute(i->children); + } +} + +void split_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->contents && i->contents->next) { + repitem_t * j; + + switch (i->kind) { + case repitem_t::TRANSACTION: + assert(0); + j = new xact_repitem_t(static_cast(i)->xact); + break; + case repitem_t::ENTRY: + j = new entry_repitem_t(static_cast(i)->entry); + break; + case repitem_t::ACCOUNT: + j = new account_repitem_t(static_cast(i)->account); + break; + default: + j = new repitem_t(i->kind); + break; + } + + j->set_parent(i->parent); + j->prev = i; + j->next = i->next; + i->next = j; + + j->contents = i->contents->next; + j->contents->prev = NULL; + j->contents->set_parent(j); + i->contents->next = NULL; + + j->last_content = i->last_content; + if (j->contents == i->last_content) + i->last_content = i->contents; + } + + if (i->children) + execute(i->children); + } +} + +void merge_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->next) { + assert(i->kind == i->next->kind); + bool merge = false; + switch (i->kind) { + case repitem_t::TRANSACTION: + assert(0); + break; + case repitem_t::ENTRY: + if (static_cast(i)->entry == + static_cast(i->next)->entry) + merge = true; + break; + case repitem_t::ACCOUNT: +#if 0 + if (static_cast(i)->account == + static_cast(i->next)->account) + merge = true; +#endif + break; + default: + break; + } + + if (merge) { + repitem_t * j = i->next; + + i->next = i->next->next; + if (i->next) + i->next->prev = i; + + for (repitem_t * k = j->contents; k; k = k->next) + k->set_parent(i); + + i->last_content->next = j->contents; + i->last_content = j->last_content; + + j->contents = NULL; + assert(! j->children); + delete j; + } + } + + if (i->children) + execute(i->children); + } +} + +namespace { +#define REPITEM_FLAGGED 0x1 + + class mark_selected : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + item->flags |= REPITEM_FLAGGED; + } + }; + + class mark_selected_and_ancestors : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + while (item->parent) { + item->flags |= REPITEM_FLAGGED; + item = item->parent; + } + } + }; + + class delete_unmarked : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->parent && ! (item->flags & REPITEM_FLAGGED)) + delete item; + } + }; + + class delete_marked : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->flags & REPITEM_FLAGGED) + delete item; + } + }; + + class clear_flags : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + item->flags = 0; + } + }; +} + +void select_transform::execute(xml::document_t * document) +{ + if (! path) { + items->clear(); + return; + } + mark_selected_and_ancestors cb1; + items->select(path, cb1); + + delete_unmarked cb2; + items->select_all(cb2); + clear_flags cb3; + items->select_all(cb3); +} + +void remove_transform::execute(xml::document_t * document) +{ + if (! path) + return; + mark_selected cb1; + items->select(path, cb1); + + delete_marked cb2; + items->select_all(cb2); + clear_flags cb3; + items->select_all(cb3); +} +#endif + +} // namespace ledger diff --git a/src/transform.h b/src/transform.h new file mode 100644 index 00000000..1a5286a1 --- /dev/null +++ b/src/transform.h @@ -0,0 +1,136 @@ +#ifndef _TRANSFORM_H +#define _TRANSFORM_H + +#include "xpath.h" + +namespace ledger { + +class transform_t { + public: + virtual ~transform_t() {} + virtual void execute(xml::document_t * document) = 0; +}; + +class check_transform : public transform_t { + // --check checks the validity of the item list. + public: + virtual void execute(xml::document_t * document); +}; + +class accounts_transform : public transform_t { + // --accounts transforms the report tree into an account-wise view. + public: + virtual void execute(xml::document_t * document); +}; + +class compact_transform : public transform_t { + // --compact compacts an account tree to remove accounts with only + // one child account. + public: + virtual void execute(xml::document_t * document); +}; + +class clean_transform : public transform_t { + // --clean clears out entries and accounts that have no contents. + public: + virtual void execute(xml::document_t * document); +}; + +class entries_transform : public transform_t { + // --entries transforms the report tree into an entries-wise view. + public: + virtual void execute(xml::document_t * document); +}; + +class optimize_transform : public transform_t { + // --optimize optimizes entries for display by the print command. + // What this means is that if an entry has two transactions of the + // commodity (one the negative of the other), the amount of the + // second transaction will be nulled out. + public: + virtual void execute(xml::document_t * document); +}; + +class split_transform : public transform_t { + // --split breaks entry with two or more transactions into what + // seems like two entries each with one transaction -- even though + // it is the same entry being reported in both cases. This is + // useful before sorting, for exampel, in order to sort by + // transaction instead of by entry. + public: + virtual void execute(xml::document_t * document); +}; + +class merge_transform : public transform_t { + // --merge is the opposite of --split: any adjacent transactions + // which share the same entry will be merged into a group of + // transactions under one reported entry. + public: + virtual void execute(xml::document_t * document); +}; + +class combine_transform : public transform_t { + // --combine EXPR combines all transactions matching EXPR so that + // they appear within the same virtual entry (whose date will span + // the earliest to the latest of those entries, and whose payee name + // will show the terminating date or a label that is characteristic + // of the set). + public: + virtual void execute(xml::document_t * document); +}; + +class group_transform : public transform_t { + // --group groups all transactions that affect the same account + // within an entry, so that they appear as a single transaction. + public: + virtual void execute(xml::document_t * document); +}; + +class collapse_transform : public transform_t { + // --collapse makes all transactions within an entry appear as a + // single transaction, even if they affect different accounts. The + // fictitous account "" is used to represent the final sum, + // if multiple accounts are involved. + public: + virtual void execute(xml::document_t * document); +}; + +class subtotal_transform : public transform_t { + // --subtotal will combine the transactions from all entries into + // one giant entry. When used in conjunction with --group, the + // affect is very similar to a regular balance report. + public: + virtual void execute(xml::document_t * document); +}; + +#if 0 +class select_transform : public transform_t +{ + protected: + xml::xpath_t xpath; + + public: + select_transform(const string& selection_path) { + xpath.parse(selection_path); + } + virtual ~select_transform() { + if (path) + delete path; + } + + virtual void execute(xml::document_t * document); +}; + +class remove_transform : public select_transform +{ + public: + remove_transform(const string& selection_path) + : select_transform(selection_path) {} + + virtual void execute(xml::document_t * document); +}; +#endif + +} // namespace ledger + +#endif // _TRANSFORM_H diff --git a/src/utils.cc b/src/utils.cc new file mode 100644 index 00000000..63c28c5c --- /dev/null +++ b/src/utils.cc @@ -0,0 +1,687 @@ +#include "utils.h" +#include "times.h" + +/********************************************************************** + * + * Assertions + */ + +#if defined(ASSERTS_ON) + +namespace ledger { + +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 + << ": " << reason; + throw exception(buf.str(), context()); +} + +} // namespace ledger + +#endif + +/********************************************************************** + * + * Verification (basically, very slow asserts) + */ + +#if defined(VERIFY_ON) + +namespace ledger { + +bool verify_enabled = false; + +typedef std::pair allocation_pair; +typedef std::map live_memory_map; +typedef std::pair live_memory_pair; +typedef std::multimap live_objects_map; +typedef std::pair live_objects_pair; +typedef std::pair count_size_pair; +typedef std::map object_count_map; +typedef std::pair object_count_pair; + +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); + } + + delete live_memory; live_memory = NULL; + delete live_memory_count; live_memory_count = NULL; + delete total_memory_count; total_memory_count = NULL; + + delete live_objects; live_objects = NULL; + delete live_object_count; live_object_count = NULL; + delete total_object_count; total_object_count = NULL; + 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 result = + the_map.insert(object_count_pair(name, count_size_pair(1, size))); + VERIFY(result.second); + } +} + +std::size_t current_memory_size() +{ + std::size_t memory_size = 0; + + for (object_count_map::const_iterator i = live_memory_count->begin(); + i != live_memory_count->end(); + i++) + memory_size += (*i).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_pair(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) +{ + for (object_count_map::iterator i = the_map.begin(); + i != the_map.end(); + i++) + out << " " << std::right << std::setw(12) << (*i).second.first + << " " << std::right << std::setw(12) << (*i).second.second + << " " << std::left << (*i).first + << std::endl; +} + +std::size_t current_objects_size() +{ + std::size_t objects_size = 0; + + for (object_count_map::const_iterator i = live_object_count->begin(); + i != live_object_count->end(); + i++) + objects_size += (*i).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_("verify.memory", "TRACE_CTOR " << ptr << " " << name); + + live_objects->insert(live_objects_pair(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_("ledger.trace.debug", "TRACE_DTOR " << ptr << " " << cls_name); + + live_objects_map::iterator i = live_objects->find(ptr); + VERIFY(i != live_objects->end()); + + 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; + + for (live_memory_map::const_iterator i = live_memory->begin(); + i != live_memory->end(); + i++) + out << " " << std::right << std::setw(7) << (*i).first + << " " << std::right << std::setw(7) << (*i).second.second + << " " << std::left << (*i).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; + + for (live_objects_map::const_iterator i = live_objects->begin(); + i != live_objects->end(); + i++) + out << " " << std::right << std::setw(7) << (*i).first + << " " << std::right << std::setw(7) << (*i).second.second + << " " << std::left << (*i).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); + } + } +} + +#if ! defined(USE_BOOST_PYTHON) + +string::string() : std::string() { + TRACE_CTOR(string, ""); +} +string::string(const string& str) : std::string(str) { + TRACE_CTOR(string, "const string&"); +} +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() { + TRACE_DTOR(string); +} + +#endif + +} // namespace ledger + +#endif // VERIFY_ON + +/********************************************************************** + * + * Logging + */ + +#if defined(LOGGING_ON) + +namespace ledger { + +log_level_t _log_level; +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 if (size < (1024 * 1024 * 1024 * 1024)) + out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; + else + assert(false); +} + +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 - now).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) + +#include + +namespace ledger { + +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 timer_map; +typedef std::pair timer_pair; + +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_pair(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 _exc_buffer; + +} // namespace ledger + +/********************************************************************** + * + * General utility functions + */ + +namespace ledger { + +string expand_path(const string& path) +{ + if (path.length() == 0 || path[0] != '~') + return path; + + const char * pfx = NULL; + 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 { + string user(path, 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 path; + + string result(pfx); + + if (pos == string::npos) + return result; + + if (result.length() == 0 || result[result.length() - 1] != '/') + result += '/'; + + result += path.substr(pos + 1); + + return result; +} + +string resolve_path(const string& path) +{ + if (path[0] == '~') + return expand_path(path); + return path; +} + +} // namespace ledger diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..79595c71 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,422 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include + +/********************************************************************** + * + * Forward declarations + */ + +namespace ledger { +#if ! defined(USE_BOOST_PYTHON) + class string; +#else + typedef std::string string; +#endif +} + +/********************************************************************** + * + * Default values + */ + +#if defined(FULL_DEBUG) +#define VERIFY_ON 1 +#define TRACING_ON 1 +#define DEBUG_ON 1 +#define TIMERS_ON 1 +#define FREE_MEMORY 1 +#elif defined(NO_DEBUG) +#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 // jww (2007-04-25): is this correct? +#endif + +/********************************************************************** + * + * Assertions + */ + +#ifdef assert +#undef assert +#endif + +#if ! defined(NO_ASSERTS) +#define ASSERTS_ON 1 +#endif +#if defined(ASSERTS_ON) + +#include + +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__)) + +#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 +#define IF_VERIFY() if (DO_VERIFY()) + +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() ? trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) +#define TRACE_DTOR(cls) \ + (DO_VERIFY() ? trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) + +void report_memory(std::ostream& out, bool report_all = false); + +#if ! defined(USE_BOOST_PYTHON) + +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(); +}; + +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; } + +#endif + +} // namespace ledger + +#else // ! VERIFY_ON + +#define VERIFY(x) +#define TRACE_CTOR(cls, args) +#define TRACE_DTOR(cls) + +#endif // VERIFY_ON + +/********************************************************************** + * + * 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) \ + (_log_level >= LOG_TRACE && lvl <= _trace_level) +#define TRACE(lvl, msg) \ + (SHOW_TRACE(lvl) ? ((_log_buffer << msg), logger_func(LOG_TRACE)) : false) + +#else // TRACING_ON + +#define SHOW_TRACE(lvl) false +#define TRACE(lvl, msg) + +#endif // TRACING_ON + +#if defined(DEBUG_ON) + +extern std::string _log_category; + +inline bool category_matches(const char * cat) { + return (_log_category == cat || + (std::strlen(cat) > _log_category.size() + 1 && + std::strncmp(cat, _log_category.c_str(), + _log_category.size()) == 0 && + cat[_log_category.size()] == '.')); +} + +#define SHOW_DEBUG_(cat) \ + (_log_level >= LOG_DEBUG && category_matches(cat)) +#define SHOW_DEBUG() SHOW_DEBUG_(_this_category) + +#define DEBUG_(cat, msg) \ + (SHOW_DEBUG_(cat) ? ((_log_buffer << msg), logger_func(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) \ + (_log_level >= level ? \ + ((_log_buffer << msg), logger_func(level)) : false) + +#define SHOW_INFO() (_log_level >= LOG_INFO) +#define SHOW_WARN() (_log_level >= LOG_WARN) +#define SHOW_ERROR() (_log_level >= LOG_ERROR) +#define SHOW_FATAL() (_log_level >= LOG_FATAL) +#define SHOW_CRITICAL() (_log_level >= LOG_CRIT) + +#define INFO(msg) LOG_MACRO(LOG_INFO, msg) +#define WARN(msg) LOG_MACRO(LOG_WARN, msg) +#define ERROR(msg) LOG_MACRO(LOG_ERROR, msg) +#define FATAL(msg) LOG_MACRO(LOG_FATAL, msg) +#define CRITICAL(msg) LOG_MACRO(LOG_CRIT, msg) +#define EXCEPTION(msg) LOG_MACRO(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(msg) +#define DEBUG_(cat, 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) ? \ + ((_log_buffer << msg), start_timer(#name, LOG_TRACE)) : ((void)0)) +#define TRACE_STOP(name, lvl) \ + (SHOW_TRACE(lvl) ? stop_timer(#name) : ((void)0)) +#define TRACE_FINISH(name, lvl) \ + (SHOW_TRACE(lvl) ? 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) ? \ + ((_log_buffer << msg), start_timer(#name, LOG_DEBUG)) : ((void)0)) +#define DEBUG_START(name, msg) \ + DEBUG_START_(name, _this_category, msg) +#define DEBUG_STOP_(name, cat) \ + (SHOW_DEBUG_(cat) ? stop_timer(#name) : ((void)0)) +#define DEBUG_STOP(name) \ + DEBUG_STOP_(name, _this_category) +#define DEBUG_FINISH_(name, cat) \ + (SHOW_DEBUG_(cat) ? finish_timer(#name) : ((void)0)) +#define DEBUG_FINISH(name) \ + DEBUG_FINISH_(name, _this_category) +#else +#define DEBUG_START(name, msg) +#define DEBUG_START_(name, cat, msg) +#define DEBUG_STOP(name) +#define DEBUG_FINISH(name) +#endif + +#define INFO_START(name, msg) \ + (SHOW_INFO() ? \ + ((_log_buffer << msg), start_timer(#name, 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 + */ + +#include "error.h" + +namespace ledger { + +extern std::ostringstream _exc_buffer; + +template +inline void throw_func(const std::string& message) { + _exc_buffer.str(""); + throw T(message, context()); +} + +#define throw_(cls, msg) \ + ((_exc_buffer << msg), throw_func(_exc_buffer.str())) + +} // namespace ledger + +/********************************************************************** + * + * General utility functions + */ + +namespace ledger { + +string resolve_path(const string& path); + +#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; +} + +} // namespace ledger + +#endif // _UTILS_H diff --git a/src/value.cc b/src/value.cc new file mode 100644 index 00000000..914a49e2 --- /dev/null +++ b/src/value.cc @@ -0,0 +1,2311 @@ +#include "value.h" +#include "xml.h" + +namespace ledger { + +bool value_t::to_boolean() const +{ + if (type == BOOLEAN) { + return *(bool *) data; + } else { + value_t temp(*this); + temp.in_place_cast(BOOLEAN); + return *(bool *) temp.data; + } +} + +long value_t::to_integer() const +{ + if (type == INTEGER) { + return *(long *) data; + } else { + value_t temp(*this); + temp.in_place_cast(INTEGER); + return *(long *) temp.data; + } +} + +moment_t value_t::to_datetime() const +{ + if (type == DATETIME) { + return *(moment_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(DATETIME); + return *(moment_t *) temp.data; + } +} + +amount_t value_t::to_amount() const +{ + if (type == AMOUNT) { + return *(amount_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(AMOUNT); + return *(amount_t *) temp.data; + } +} + +balance_t value_t::to_balance() const +{ + if (type == BALANCE) { + return *(balance_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE); + return *(balance_t *) temp.data; + } +} + +balance_pair_t value_t::to_balance_pair() const +{ + if (type == BALANCE_PAIR) { + return *(balance_pair_t *) data; + } else { + value_t temp(*this); + temp.in_place_cast(BALANCE_PAIR); + return *(balance_pair_t *) temp.data; + } +} + +string value_t::to_string() const +{ + if (type == STRING) { + return **(string **) data; + } else { + std::ostringstream out; + out << *this; + return out.str(); + } +} + +xml::node_t * value_t::to_xml_node() const +{ + if (type == XML_NODE) + return *(xml::node_t **) data; + else + throw_(value_exception, "Value is not an XML node"); +} + +void * value_t::to_pointer() const +{ + if (type == POINTER) + return *(void **) data; + else + throw_(value_exception, "Value is not a pointer"); +} + +value_t::sequence_t * value_t::to_sequence() const +{ + if (type == SEQUENCE) + return *(sequence_t **) data; + else + throw_(value_exception, "Value is not a sequence"); +} + +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; + case STRING: + delete *(string **) data; + break; + case SEQUENCE: + delete *(sequence_t **) data; + break; + default: + break; + } +} + +void value_t::simplify() +{ + if (realzero()) { + DEBUG_("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_("amounts.values.simplify", "Reducing balance pair to balance"); + in_place_cast(BALANCE); + } + + if (type == BALANCE && + ((balance_t *) data)->amounts.size() == 1) { + DEBUG_("amounts.values.simplify", "Reducing balance to amount"); + in_place_cast(AMOUNT); + } + + if (type == AMOUNT && + ! ((amount_t *) data)->commodity()) { + DEBUG_("amounts.values.simplify", "Reducing amount to integer"); + in_place_cast(INTEGER); + } +} + +value_t& value_t::operator=(const value_t& val) +{ + if (this == &val) + return *this; + + if (type == BOOLEAN && val.type == BOOLEAN) { + *((bool *) data) = *((bool *) val.data); + return *this; + } + else if (type == INTEGER && val.type == INTEGER) { + *((long *) data) = *((long *) val.data); + return *this; + } + else if (type == DATETIME && val.type == DATETIME) { + *((moment_t *) data) = *((moment_t *) val.data); + return *this; + } + else if (type == AMOUNT && val.type == AMOUNT) { + *(amount_t *) data = *(amount_t *) val.data; + return *this; + } + else if (type == BALANCE && val.type == BALANCE) { + *(balance_t *) data = *(balance_t *) val.data; + return *this; + } + else if (type == BALANCE_PAIR && val.type == BALANCE_PAIR) { + *(balance_pair_t *) data = *(balance_pair_t *) val.data; + return *this; + } + else if (type == STRING && val.type == STRING) { + **(string **) data = **(string **) val.data; + return *this; + } + else if (type == SEQUENCE && val.type == SEQUENCE) { + **(sequence_t **) data = **(sequence_t **) val.data; + return *this; + } + + destroy(); + + switch (val.type) { + case BOOLEAN: + *((bool *) data) = *((bool *) val.data); + break; + + case INTEGER: + *((long *) data) = *((long *) val.data); + break; + + case DATETIME: + *((moment_t *) data) = *((moment_t *) val.data); + break; + + case AMOUNT: + new((amount_t *)data) amount_t(*((amount_t *) val.data)); + break; + + case BALANCE: + new((balance_t *)data) balance_t(*((balance_t *) val.data)); + break; + + case BALANCE_PAIR: + new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) val.data)); + break; + + case STRING: + *(string **) data = new string(**(string **) val.data); + break; + + case XML_NODE: + *(xml::node_t **) data = *(xml::node_t **) val.data; + break; + + case POINTER: + *(void **) data = *(void **) val.data; + break; + + case SEQUENCE: + *(sequence_t **) data = new sequence_t(**(sequence_t **) val.data); + break; + + default: + assert(0); + break; + } + + type = val.type; + + return *this; +} + +value_t& value_t::operator+=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot add a boolean to a value"); + else if (val.type == DATETIME) + throw_(value_exception, "Cannot add a date/time to a value"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot add a pointer to a value"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot add a sequence to a value"); + else if (val.type == XML_NODE) // recurse + return *this += (*(xml::node_t **) val.data)->to_value(); + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot add a value to a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) += *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) += *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) += *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an integer"); + default: + assert(0); + break; + } + break; + + case DATETIME: + switch (val.type) { + case INTEGER: + *((moment_t *) data) += date_duration(*((long *) val.data)); + break; + case AMOUNT: + *((moment_t *) data) += date_duration(long(*((amount_t *) val.data))); + break; + case BALANCE: + *((moment_t *) data) += date_duration(long(*((balance_t *) val.data))); + break; + case BALANCE_PAIR: + *((moment_t *) data) += date_duration(long(*((balance_pair_t *) val.data))); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an date/time"); + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + if (*((long *) val.data) && + ((amount_t *) data)->commodity()) { + in_place_cast(BALANCE); + return *this += val; + } + *((amount_t *) data) += *((long *) val.data); + break; + + case AMOUNT: + if (((amount_t *) data)->commodity() != + ((amount_t *) val.data)->commodity()) { + in_place_cast(BALANCE); + return *this += val; + } + *((amount_t *) data) += *((amount_t *) val.data); + break; + + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) += *((balance_t *) val.data); + break; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + + case STRING: + throw_(value_exception, "Cannot add a string to an amount"); + + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) += *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) += *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) += *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an balance"); + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) += *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) += *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) += *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) += *((balance_pair_t *) val.data); + break; + case STRING: + throw_(value_exception, "Cannot add a string to an balance pair"); + default: + assert(0); + break; + } + break; + + case STRING: + switch (val.type) { + case INTEGER: + throw_(value_exception, "Cannot add an integer to a string"); + case AMOUNT: + throw_(value_exception, "Cannot add an amount to a string"); + case BALANCE: + throw_(value_exception, "Cannot add a balance to a string"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot add a balance pair to a string"); + case STRING: + **(string **) data += **(string **) val.data; + break; + default: + assert(0); + break; + } + break; + + case XML_NODE: + throw_(value_exception, "Cannot add a value to an XML node"); + + case POINTER: + throw_(value_exception, "Cannot add a value to a pointer"); + + case SEQUENCE: + throw_(value_exception, "Cannot add a value to a sequence"); + + default: + assert(0); + break; + } + return *this; +} + +value_t& value_t::operator-=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot subtract a boolean from a value"); + else if (val.type == DATETIME && type != DATETIME) + throw_(value_exception, "Cannot subtract a date/time from a value"); + else if (val.type == STRING) + throw_(value_exception, "Cannot subtract a string from a value"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot subtract a pointer from a value"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot subtract a sequence from a value"); + else if (val.type == XML_NODE) // recurse + return *this -= (*(xml::node_t **) val.data)->to_value(); + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot subtract a value from a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) -= *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) -= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) -= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case DATETIME: + switch (val.type) { + case INTEGER: + *((moment_t *) data) -= date_duration(*((long *) val.data)); + break; + case DATETIME: { + duration_t tval = ((moment_t *) data)->operator-(*((moment_t *) val.data)); + in_place_cast(INTEGER); + *((long *) data) = tval.total_seconds() / 86400L; + break; + } + case AMOUNT: + *((moment_t *) data) -= date_duration(long(*((amount_t *) val.data))); + break; + case BALANCE: + *((moment_t *) data) -= date_duration(long(*((balance_t *) val.data))); + break; + case BALANCE_PAIR: + *((moment_t *) data) -= date_duration(long(*((balance_pair_t *) val.data))); + break; + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + if (*((long *) val.data) && + ((amount_t *) data)->commodity()) { + in_place_cast(BALANCE); + return *this -= val; + } + *((amount_t *) data) -= *((long *) val.data); + break; + + case AMOUNT: + if (((amount_t *) data)->commodity() != + ((amount_t *) val.data)->commodity()) { + in_place_cast(BALANCE); + return *this -= val; + } + *((amount_t *) data) -= *((amount_t *) val.data); + break; + + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) -= *((balance_t *) val.data); + break; + + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) -= *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) -= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) -= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) -= *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) -= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) -= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case STRING: + throw_(value_exception, "Cannot subtract a value from a string"); + case XML_NODE: + throw_(value_exception, "Cannot subtract a value from an XML node"); + case POINTER: + throw_(value_exception, "Cannot subtract a value from a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot subtract a value from a sequence"); + + default: + assert(0); + break; + } + + simplify(); + + return *this; +} + +value_t& value_t::operator*=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot multiply a value by a boolean"); + else if (val.type == DATETIME) + throw_(value_exception, "Cannot multiply a value by a date/time"); + else if (val.type == STRING) + throw_(value_exception, "Cannot multiply a value by a string"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot multiply a value by a pointer"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot multiply a value by a sequence"); + else if (val.type == XML_NODE) // recurse + return *this *= (*(xml::node_t **) val.data)->to_value(); + + if (val.realzero() && type != STRING) { + *this = 0L; + return *this; + } + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot multiply a value by a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) *= *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + *((amount_t *) data) *= *((long *) val.data); + break; + case AMOUNT: + *((amount_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) *= *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) *= *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) *= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) *= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case STRING: + switch (val.type) { + case INTEGER: { + string temp; + for (long i = 0; i < *(long *) val.data; i++) + temp += **(string **) data; + **(string **) data = temp; + break; + } + case AMOUNT: { + string temp; + value_t num(val); + num.in_place_cast(INTEGER); + for (long i = 0; i < *(long *) num.data; i++) + temp += **(string **) data; + **(string **) data = temp; + break; + } + case BALANCE: + throw_(value_exception, "Cannot multiply a string by a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot multiply a string by a balance pair"); + default: + assert(0); + break; + } + break; + + case XML_NODE: + throw_(value_exception, "Cannot multiply an XML node by a value"); + case POINTER: + throw_(value_exception, "Cannot multiply a pointer by a value"); + case SEQUENCE: + throw_(value_exception, "Cannot multiply a sequence by a value"); + + default: + assert(0); + break; + } + return *this; +} + +value_t& value_t::operator/=(const value_t& val) +{ + if (val.type == BOOLEAN) + throw_(value_exception, "Cannot divide a boolean by a value"); + else if (val.type == DATETIME) + throw_(value_exception, "Cannot divide a date/time by a value"); + else if (val.type == STRING) + throw_(value_exception, "Cannot divide a string by a value"); + else if (val.type == POINTER) + throw_(value_exception, "Cannot divide a pointer by a value"); + else if (val.type == SEQUENCE) + throw_(value_exception, "Cannot divide a value by a sequence"); + else if (val.type == XML_NODE) // recurse + return *this /= (*(xml::node_t **) val.data)->to_value(); + + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot divide a value by a boolean"); + + case INTEGER: + switch (val.type) { + case INTEGER: + *((long *) data) /= *((long *) val.data); + break; + case AMOUNT: + in_place_cast(AMOUNT); + *((amount_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case AMOUNT: + switch (val.type) { + case INTEGER: + *((amount_t *) data) /= *((long *) val.data); + break; + case AMOUNT: + *((amount_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + in_place_cast(BALANCE); + *((balance_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE: + switch (val.type) { + case INTEGER: + *((balance_t *) data) /= *((long *) val.data); + break; + case AMOUNT: + *((balance_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + in_place_cast(BALANCE_PAIR); + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case BALANCE_PAIR: + switch (val.type) { + case INTEGER: + *((balance_pair_t *) data) /= *((long *) val.data); + break; + case AMOUNT: + *((balance_pair_t *) data) /= *((amount_t *) val.data); + break; + case BALANCE: + *((balance_pair_t *) data) /= *((balance_t *) val.data); + break; + case BALANCE_PAIR: + *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); + break; + default: + assert(0); + break; + } + break; + + case STRING: + throw_(value_exception, "Cannot divide a value from a string"); + case XML_NODE: + throw_(value_exception, "Cannot divide a value from an XML node"); + case POINTER: + throw_(value_exception, "Cannot divide a value from a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot divide a value from a sequence"); + + default: + assert(0); + break; + } + return *this; +} + +template <> +value_t::operator bool() const +{ + switch (type) { + case BOOLEAN: + return *(bool *) data; + case INTEGER: + return *(long *) data; + case DATETIME: + return is_valid_moment(*((moment_t *) data)); + case AMOUNT: + return *(amount_t *) data; + case BALANCE: + return *(balance_t *) data; + case BALANCE_PAIR: + return *(balance_pair_t *) data; + case STRING: + return ! (**((string **) data)).empty(); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_boolean(); + case POINTER: + return *(void **) data != NULL; + case SEQUENCE: + return (*(sequence_t **) data != NULL && + ! (*(sequence_t **) data)->empty()); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator long() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a boolean to an integer"); + case INTEGER: + return *((long *) data); + case DATETIME: + throw_(value_exception, "Cannot convert a date/time to an integer"); + case AMOUNT: + return *((amount_t *) data); + case BALANCE: + throw_(value_exception, "Cannot convert a balance to an integer"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a balance pair to an integer"); + case STRING: + throw_(value_exception, "Cannot convert a string to an integer"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_integer(); + case POINTER: + throw_(value_exception, "Cannot convert a pointer to an integer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to an integer"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator moment_t() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a boolean to a date/time"); + case INTEGER: + throw_(value_exception, "Cannot convert an integer to a date/time"); + case DATETIME: + return *((moment_t *) data); + case AMOUNT: + throw_(value_exception, "Cannot convert an amount to a date/time"); + case BALANCE: + throw_(value_exception, "Cannot convert a balance to a date/time"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a balance pair to a date/time"); + case STRING: + throw_(value_exception, "Cannot convert a string to a date/time"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_datetime(); + case POINTER: + throw_(value_exception, "Cannot convert a pointer to a date/time"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to a date/time"); + + default: + assert(0); + break; + } + assert(0); + return moment_t(); +} + +template <> +value_t::operator double() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a boolean to a double"); + case INTEGER: + return *((long *) data); + case DATETIME: + throw_(value_exception, "Cannot convert a date/time to a double"); + case AMOUNT: + return *((amount_t *) data); + case BALANCE: + throw_(value_exception, "Cannot convert a balance to a double"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a balance pair to a double"); + case STRING: + throw_(value_exception, "Cannot convert a string to a double"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_amount().number(); + case POINTER: + throw_(value_exception, "Cannot convert a pointer to a double"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to a double"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator string() const +{ + switch (type) { + case BOOLEAN: + case INTEGER: + case DATETIME: + case AMOUNT: + case BALANCE: + case BALANCE_PAIR: { + value_t temp(*this); + temp.in_place_cast(STRING); + return temp; + } + case STRING: + return **(string **) data; + case XML_NODE: + return (*(xml::node_t **) data)->to_value().to_string(); + + case POINTER: + throw_(value_exception, "Cannot convert a pointer to a string"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a sequence to a string"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +#define DEF_VALUE_CMP_OP(OP) \ +bool value_t::operator OP(const value_t& val) \ +{ \ + switch (type) { \ + case BOOLEAN: \ + switch (val.type) { \ + case BOOLEAN: \ + return *((bool *) data) OP *((bool *) val.data); \ + \ + case INTEGER: \ + return *((bool *) data) OP bool(*((long *) val.data)); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a boolean to a date/time"); \ + \ + case AMOUNT: \ + return *((bool *) data) OP bool(*((amount_t *) val.data)); \ + \ + case BALANCE: \ + return *((bool *) data) OP bool(*((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return *((bool *) data) OP bool(*((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare a boolean to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a boolean to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a boolean to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case INTEGER: \ + switch (val.type) { \ + case BOOLEAN: \ + return (*((long *) data) OP \ + ((long) *((bool *) val.data))); \ + \ + case INTEGER: \ + return (*((long *) data) OP *((long *) val.data)); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare an integer to a date/time"); \ + \ + case AMOUNT: \ + return (amount_t(*((long *) data)) OP \ + *((amount_t *) val.data)); \ + \ + case BALANCE: \ + return (balance_t(*((long *) data)) OP \ + *((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return (balance_pair_t(*((long *) data)) OP \ + *((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare an integer to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare an integer to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare an integer to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case DATETIME: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a date/time to a boolean"); \ + \ + case INTEGER: \ + throw_(value_exception, "Cannot compare a date/time to an integer"); \ + \ + case DATETIME: \ + return *((moment_t *) data) OP *((moment_t *) val.data); \ + \ + case AMOUNT: \ + throw_(value_exception, "Cannot compare a date/time to an amount"); \ + case BALANCE: \ + throw_(value_exception, "Cannot compare a date/time to a balance"); \ + case BALANCE_PAIR: \ + throw_(value_exception, "Cannot compare a date/time to a balance pair"); \ + case STRING: \ + throw_(value_exception, "Cannot compare a date/time to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a date/time to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a date/time to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case AMOUNT: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare an amount to a boolean"); \ + \ + case INTEGER: \ + return (*((amount_t *) data) OP \ + amount_t(*((long *) val.data))); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare an amount to a date/time"); \ + \ + case AMOUNT: \ + return *((amount_t *) data) OP *((amount_t *) val.data); \ + \ + case BALANCE: \ + return (balance_t(*((amount_t *) data)) OP \ + *((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return (balance_t(*((amount_t *) data)) OP \ + *((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare an amount to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare an amount to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare an amount to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case BALANCE: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a balance to a boolean"); \ + \ + case INTEGER: \ + return *((balance_t *) data) OP *((long *) val.data); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a balance to a date/time"); \ + \ + case AMOUNT: \ + return *((balance_t *) data) OP *((amount_t *) val.data); \ + \ + case BALANCE: \ + return *((balance_t *) data) OP *((balance_t *) val.data); \ + \ + case BALANCE_PAIR: \ + return (*((balance_t *) data) OP \ + ((balance_pair_t *) val.data)->quantity); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare a balance to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a balance to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a balance to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case BALANCE_PAIR: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a balance pair to a boolean"); \ + \ + case INTEGER: \ + return (((balance_pair_t *) data)->quantity OP \ + *((long *) val.data)); \ + \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a balance pair to a date/time"); \ + \ + case AMOUNT: \ + return (((balance_pair_t *) data)->quantity OP \ + *((amount_t *) val.data)); \ + \ + case BALANCE: \ + return (((balance_pair_t *) data)->quantity OP \ + *((balance_t *) val.data)); \ + \ + case BALANCE_PAIR: \ + return (*((balance_pair_t *) data) OP \ + *((balance_pair_t *) val.data)); \ + \ + case STRING: \ + throw_(value_exception, "Cannot compare a balance pair to a string"); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a balance pair to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a balance pair to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case STRING: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a string to a boolean"); \ + case INTEGER: \ + throw_(value_exception, "Cannot compare a string to an integer"); \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a string to a date/time"); \ + case AMOUNT: \ + throw_(value_exception, "Cannot compare a string to an amount"); \ + case BALANCE: \ + throw_(value_exception, "Cannot compare a string to a balance"); \ + case BALANCE_PAIR: \ + throw_(value_exception, "Cannot compare a string to a balance pair"); \ + \ + case STRING: \ + return (**((string **) data) OP \ + **((string **) val.data)); \ + \ + case XML_NODE: \ + return *this OP (*(xml::node_t **) data)->to_value(); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare a string to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a string to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case XML_NODE: \ + switch (val.type) { \ + case BOOLEAN: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case INTEGER: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case DATETIME: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case AMOUNT: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case BALANCE: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case BALANCE_PAIR: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + case STRING: \ + return (*(xml::node_t **) data)->to_value() OP *this; \ + \ + case XML_NODE: \ + return ((*(xml::node_t **) data)->to_value() OP \ + (*(xml::node_t **) val.data)->to_value()); \ + \ + case POINTER: \ + throw_(value_exception, "Cannot compare an XML node to a pointer"); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare an XML node to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case POINTER: \ + switch (val.type) { \ + case BOOLEAN: \ + throw_(value_exception, "Cannot compare a pointer to a boolean"); \ + case INTEGER: \ + throw_(value_exception, "Cannot compare a pointer to an integer"); \ + case DATETIME: \ + throw_(value_exception, "Cannot compare a pointer to a date/time"); \ + case AMOUNT: \ + throw_(value_exception, "Cannot compare a pointer to an amount"); \ + case BALANCE: \ + throw_(value_exception, "Cannot compare a pointer to a balance"); \ + case BALANCE_PAIR: \ + throw_(value_exception, "Cannot compare a pointer to a balance pair"); \ + case STRING: \ + throw_(value_exception, "Cannot compare a pointer to a string node"); \ + case XML_NODE: \ + throw_(value_exception, "Cannot compare a pointer to an XML node"); \ + case POINTER: \ + return (*((void **) data) OP *((void **) val.data)); \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a pointer to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case SEQUENCE: \ + throw_(value_exception, "Cannot compare a value to a sequence"); \ + \ + 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(>=) + +void value_t::in_place_cast(type_t cast_type) +{ + switch (type) { + case BOOLEAN: + switch (cast_type) { + case BOOLEAN: + break; + case INTEGER: + throw_(value_exception, "Cannot convert a boolean to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a boolean to a date/time"); + case AMOUNT: + throw_(value_exception, "Cannot convert a boolean to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a boolean to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a boolean to a balance pair"); + case STRING: + *(string **) data = new string(*((bool *) data) ? "true" : "false"); + break; + case XML_NODE: + throw_(value_exception, "Cannot convert a boolean to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a boolean to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a boolean to a sequence"); + + default: + assert(0); + break; + } + break; + + case INTEGER: + switch (cast_type) { + case BOOLEAN: + *((bool *) data) = *((long *) data); + break; + case INTEGER: + break; + case DATETIME: + throw_(value_exception, "Cannot convert an integer to a date/time"); + + case AMOUNT: + new((amount_t *)data) amount_t(*((long *) data)); + break; + case BALANCE: + new((balance_t *)data) balance_t(*((long *) data)); + break; + case BALANCE_PAIR: + new((balance_pair_t *)data) balance_pair_t(*((long *) data)); + break; + case STRING: { + char buf[32]; + std::sprintf(buf, "%ld", *(long *) data); + *(string **) data = new string(buf); + break; + } + case XML_NODE: + throw_(value_exception, "Cannot convert an integer to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert an integer to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert an integer to a sequence"); + + default: + assert(0); + break; + } + break; + + case DATETIME: + switch (cast_type) { + case BOOLEAN: + *((bool *) data) = is_valid_moment(*((moment_t *) data)); + break; + case INTEGER: + throw_(value_exception, "Cannot convert a date/time to an integer"); + case DATETIME: + break; + case AMOUNT: + throw_(value_exception, "Cannot convert a date/time to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a date/time to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a date/time to a balance pair"); + case STRING: + throw_(value_exception, "Cannot convert a date/time to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a date/time to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a date/time to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a date/time to a sequence"); + + 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_(value_exception, "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; + } + case STRING: { + std::ostringstream out; + out << *(amount_t *) data; + destroy(); + *(string **) data = new string(out.str()); + break; + } + case XML_NODE: + throw_(value_exception, "Cannot convert an amount to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert an amount to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert an amount to a sequence"); + + 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_(value_exception, "Cannot convert a balance to an integer"); + case DATETIME: + throw_(value_exception, "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_(value_exception, "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; + } + case STRING: + throw_(value_exception, "Cannot convert a balance to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a balance to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a balance to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a balance to a sequence"); + + 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_(value_exception, "Cannot convert a balance pair to an integer"); + case DATETIME: + throw_(value_exception, "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_(value_exception, "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; + case STRING: + throw_(value_exception, "Cannot convert a balance pair to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a balance pair to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a balance pair to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a balance pair to a sequence"); + + default: + assert(0); + break; + } + break; + + case STRING: + switch (cast_type) { + case BOOLEAN: { + if (**(string **) data == "true") { + destroy(); + *(bool *) data = true; + } + else if (**(string **) data == "false") { + destroy(); + *(bool *) data = false; + } + else { + throw_(value_exception, "Cannot convert string to an boolean"); + } + break; + } + case INTEGER: { + int l = (*(string **) data)->length(); + const char * p = (*(string **) data)->c_str(); + bool alldigits = true; + for (int i = 0; i < l; i++) + if (! std::isdigit(p[i])) { + alldigits = false; + break; + } + if (alldigits) { + long temp = std::atol((*(string **) data)->c_str()); + destroy(); + *(long *) data = temp; + } else { + throw_(value_exception, "Cannot convert string to an integer"); + } + break; + } + + case DATETIME: + throw_(value_exception, "Cannot convert a string to a date/time"); + + case AMOUNT: { + amount_t temp = **(string **) data; + destroy(); + new((amount_t *)data) amount_t(temp); + break; + } + case BALANCE: + throw_(value_exception, "Cannot convert a string to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a string to a balance pair"); + case STRING: + break; + case XML_NODE: + throw_(value_exception, "Cannot convert a string to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a string to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert a string to a sequence"); + + default: + assert(0); + break; + } + break; + + case XML_NODE: + switch (cast_type) { + case BOOLEAN: + case INTEGER: + case DATETIME: + case AMOUNT: + case BALANCE: + case BALANCE_PAIR: + case STRING: + *this = (*(xml::node_t **) data)->to_value(); + break; + case XML_NODE: + break; + case POINTER: + throw_(value_exception, "Cannot convert an XML node to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot convert an XML node to a sequence"); + + default: + assert(0); + break; + } + break; + + case POINTER: + switch (cast_type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a pointer to a boolean"); + case INTEGER: + throw_(value_exception, "Cannot convert a pointer to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a pointer to a date/time"); + case AMOUNT: + throw_(value_exception, "Cannot convert a pointer to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a pointer to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a pointer to a balance pair"); + case STRING: + throw_(value_exception, "Cannot convert a pointer to a string"); + case XML_NODE: + throw_(value_exception, "Cannot convert a pointer to an XML node"); + case POINTER: + break; + case SEQUENCE: + throw_(value_exception, "Cannot convert a pointer to a sequence"); + + default: + assert(0); + break; + } + break; + + case SEQUENCE: + switch (cast_type) { + case BOOLEAN: + throw_(value_exception, "Cannot convert a sequence to a boolean"); + case INTEGER: + throw_(value_exception, "Cannot convert a sequence to an integer"); + case DATETIME: + throw_(value_exception, "Cannot convert a sequence to a date/time"); + case AMOUNT: + throw_(value_exception, "Cannot convert a sequence to an amount"); + case BALANCE: + throw_(value_exception, "Cannot convert a sequence to a balance"); + case BALANCE_PAIR: + throw_(value_exception, "Cannot convert a sequence to a balance pair"); + case STRING: + throw_(value_exception, "Cannot convert a sequence to a string"); + case XML_NODE: + throw_(value_exception, "Cannot compare a sequence to an XML node"); + case POINTER: + throw_(value_exception, "Cannot convert a sequence to a pointer"); + case SEQUENCE: + break; + + default: + assert(0); + break; + } + break; + + default: + assert(0); + break; + } + type = cast_type; +} + +void value_t::in_place_negate() +{ + switch (type) { + case BOOLEAN: + *((bool *) data) = ! *((bool *) data); + break; + case INTEGER: + *((long *) data) = - *((long *) data); + break; + case DATETIME: + throw_(value_exception, "Cannot negate a date/time"); + case AMOUNT: + ((amount_t *) data)->in_place_negate(); + break; + case BALANCE: + ((balance_t *) data)->in_place_negate(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->in_place_negate(); + break; + case STRING: + throw_(value_exception, "Cannot negate a string"); + case XML_NODE: + *this = (*(xml::node_t **) data)->to_value(); + in_place_negate(); + break; + case POINTER: + throw_(value_exception, "Cannot negate a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot negate a sequence"); + + default: + assert(0); + break; + } +} + +void value_t::in_place_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; + case STRING: + throw_(value_exception, "Cannot take the absolute value of a string"); + case XML_NODE: + *this = (*(xml::node_t **) data)->to_value(); + in_place_abs(); + break; + case POINTER: + throw_(value_exception, "Cannot take the absolute value of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot take the absolute value of a sequence"); + + default: + assert(0); + break; + } +} + +value_t value_t::value(const moment_t& moment) const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the value of a boolean"); + case DATETIME: + throw_(value_exception, "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); + case STRING: + throw_(value_exception, "Cannot find the value of a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().value(moment); + case POINTER: + throw_(value_exception, "Cannot find the value of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the value of a sequence"); + default: + assert(0); + return value_t(); + } +} + +void value_t::in_place_reduce() +{ + switch (type) { + case BOOLEAN: + case DATETIME: + case INTEGER: + break; + case AMOUNT: + ((amount_t *) data)->in_place_reduce(); + break; + case BALANCE: + ((balance_t *) data)->in_place_reduce(); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->in_place_reduce(); + break; + case STRING: + throw_(value_exception, "Cannot reduce a string"); + case XML_NODE: + *this = (*(xml::node_t **) data)->to_value(); + in_place_reduce(); // recurse + break; + case POINTER: + throw_(value_exception, "Cannot reduce a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot reduce a sequence"); + } +} + +value_t value_t::round() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot round a boolean"); + case DATETIME: + throw_(value_exception, "Cannot round a date/time"); + case INTEGER: + return *this; + case AMOUNT: + return ((amount_t *) data)->round(); + case BALANCE: + return ((balance_t *) data)->round(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->round(); + case STRING: + throw_(value_exception, "Cannot round a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().round(); + case POINTER: + throw_(value_exception, "Cannot round a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot round a sequence"); + } + assert(0); + return value_t(); +} + +value_t value_t::unround() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot un-round a boolean"); + case DATETIME: + throw_(value_exception, "Cannot un-round a date/time"); + case INTEGER: + return *this; + case AMOUNT: + return ((amount_t *) data)->unround(); + case BALANCE: + return ((balance_t *) data)->unround(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->unround(); + case STRING: + throw_(value_exception, "Cannot un-round a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().unround(); + case POINTER: + throw_(value_exception, "Cannot un-round a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot un-round a sequence"); + } + assert(0); + return value_t(); +} + +value_t value_t::price() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the price of a boolean"); + case INTEGER: + return *this; + case DATETIME: + throw_(value_exception, "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(); + + case STRING: + throw_(value_exception, "Cannot find the price of a string"); + + case XML_NODE: + return (*(xml::node_t **) data)->to_value().price(); + + case POINTER: + throw_(value_exception, "Cannot find the price of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the price of a sequence"); + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + +value_t value_t::date() const +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot find the date of a boolean"); + case INTEGER: + throw_(value_exception, "Cannot find the date of an integer"); + + case DATETIME: + return *this; + + case AMOUNT: + return ((amount_t *) data)->date(); + case BALANCE: + return ((balance_t *) data)->date(); + case BALANCE_PAIR: + return ((balance_pair_t *) data)->quantity.date(); + + case STRING: + throw_(value_exception, "Cannot find the date of a string"); + + case XML_NODE: + return (*(xml::node_t **) data)->to_value().date(); + + case POINTER: + throw_(value_exception, "Cannot find the date of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the date of a sequence"); + + 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: + case STRING: + case XML_NODE: + case POINTER: + return *this; + + case SEQUENCE: + assert(0); // jww (2006-09-28): strip them all! + break; + + 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_(value_exception, "Cannot find the cost of a boolean"); + case INTEGER: + case AMOUNT: + case BALANCE: + return *this; + case DATETIME: + throw_(value_exception, "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; + + case STRING: + throw_(value_exception, "Cannot find the cost of a string"); + case XML_NODE: + return (*(xml::node_t **) data)->to_value().cost(); + case POINTER: + throw_(value_exception, "Cannot find the cost of a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot find the cost of a sequence"); + + default: + assert(0); + break; + } + assert(0); + return value_t(); +} + +value_t& value_t::add(const amount_t& amount, const amount_t * tcost) +{ + switch (type) { + case BOOLEAN: + throw_(value_exception, "Cannot add an amount to a boolean"); + case DATETIME: + throw_(value_exception, "Cannot add an amount to a date/time"); + case INTEGER: + case AMOUNT: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + else if ((type == AMOUNT && + ((amount_t *) data)->commodity() != amount.commodity()) || + (type != AMOUNT && amount.commodity())) { + in_place_cast(BALANCE); + return add(amount, tcost); + } + else if (type != AMOUNT) { + in_place_cast(AMOUNT); + } + *((amount_t *) data) += amount; + break; + + case BALANCE: + if (tcost) { + in_place_cast(BALANCE_PAIR); + return add(amount, tcost); + } + *((balance_t *) data) += amount; + break; + + case BALANCE_PAIR: + ((balance_pair_t *) data)->add(amount, tcost); + break; + + case STRING: + throw_(value_exception, "Cannot add an amount to a string"); + case XML_NODE: + throw_(value_exception, "Cannot add an amount to an XML node"); + case POINTER: + throw_(value_exception, "Cannot add an amount to a pointer"); + case SEQUENCE: + throw_(value_exception, "Cannot add an amount to a sequence"); + + default: + assert(0); + break; + } + + return *this; +} + +void value_t::write(std::ostream& out, const int first_width, + const int latter_width) const +{ + switch (type) { + case BOOLEAN: + case DATETIME: + case INTEGER: + case AMOUNT: + case STRING: + case POINTER: + out << *this; + break; + + case XML_NODE: + (*(xml::node_t **) data)->write(out); + break; + + case SEQUENCE: + assert(0); // jww (2006-09-28): write them all out! + throw_(value_exception, "Cannot write out a sequence"); + + case BALANCE: + ((balance_t *) data)->write(out, first_width, latter_width); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->write(out, first_width, latter_width); + break; + } +} + +std::ostream& operator<<(std::ostream& out, const value_t& val) +{ + switch (val.type) { + case value_t::BOOLEAN: + out << (*((bool *) val.data) ? "true" : "false"); + break; + case value_t::INTEGER: + out << *(long *) val.data; + break; + case value_t::DATETIME: + out << *(moment_t *) val.data; + break; + case value_t::AMOUNT: + out << *(amount_t *) val.data; + break; + case value_t::BALANCE: + out << *(balance_t *) val.data; + break; + case value_t::BALANCE_PAIR: + out << *(balance_pair_t *) val.data; + break; + case value_t::STRING: + out << **(string **) val.data; + break; + case value_t::XML_NODE: + if ((*(xml::node_t **) val.data)->flags & XML_NODE_IS_PARENT) + out << '<' << (*(xml::node_t **) val.data)->name() << '>'; + else + out << (*(xml::node_t **) val.data)->text(); + break; + + case value_t::POINTER: + throw_(value_exception, "Cannot output a pointer value"); + + case value_t::SEQUENCE: { + out << '('; + bool first = true; + for (value_t::sequence_t::iterator + i = (*(value_t::sequence_t **) val.data)->begin(); + i != (*(value_t::sequence_t **) val.data)->end(); + i++) { + if (first) + first = false; + else + out << ", "; + out << *i; + } + out << ')'; + break; + } + + default: + assert(0); + break; + } + return out; +} + +#if 0 +value_context::value_context(const value_t& _bal, + const string& _desc) throw() + : error_context(_desc), bal(new value_t(_bal)) {} + +value_context::~value_context() throw() +{ + delete bal; +} + +void value_context::describe(std::ostream& out) const throw() +{ + if (! desc.empty()) + out << desc << std::endl; + + balance_t * ptr = NULL; + + out << std::right; + out.width(20); + + switch (bal->type) { + case value_t::BOOLEAN: + out << (*((bool *) bal->data) ? "true" : "false"); + break; + case value_t::INTEGER: + out << *((long *) bal->data); + break; + case value_t::DATETIME: + out << *((moment_t *) bal->data); + break; + case value_t::AMOUNT: + out << *((amount_t *) bal->data); + break; + case value_t::BALANCE: + ptr = (balance_t *) bal->data; + // fall through... + + case value_t::BALANCE_PAIR: + if (! ptr) + ptr = &((balance_pair_t *) bal->data)->quantity; + + ptr->write(out, 20); + break; + default: + assert(0); + break; + } + out << std::endl; +} +#endif + +} // namespace ledger diff --git a/src/value.h b/src/value.h new file mode 100644 index 00000000..2b0adf2b --- /dev/null +++ b/src/value.h @@ -0,0 +1,580 @@ +#ifndef _VALUE_H +#define _VALUE_H + +#include "amount.h" +#include "balance.h" + +namespace ledger { + +namespace xml { + class node_t; +} + +// 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: + typedef std::vector sequence_t; + + char data[sizeof(balance_pair_t)]; + + enum type_t { + BOOLEAN, + INTEGER, + DATETIME, + AMOUNT, + BALANCE, + BALANCE_PAIR, + STRING, + XML_NODE, + POINTER, + SEQUENCE + } type; + + value_t() { + TRACE_CTOR(value_t, ""); + *((long *) data) = 0; + type = INTEGER; + } + + value_t(const value_t& val) : type(INTEGER) { + TRACE_CTOR(value_t, "copy"); + *this = val; + } + value_t(const bool val) { + TRACE_CTOR(value_t, "const bool"); + *((bool *) data) = val; + type = BOOLEAN; + } + value_t(const long val) { + TRACE_CTOR(value_t, "const long"); + *((long *) data) = val; + type = INTEGER; + } + value_t(const moment_t val) { + TRACE_CTOR(value_t, "const moment_t"); + *((moment_t *) data) = val; + type = DATETIME; + } + value_t(const unsigned long val) { + TRACE_CTOR(value_t, "const unsigned long"); + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + value_t(const double val) { + TRACE_CTOR(value_t, "const double"); + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + value_t(const string& val, bool literal = false) { + TRACE_CTOR(value_t, "const string&, bool"); + if (literal) { + type = INTEGER; + set_string(val); + } else { + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + } + value_t(const char * val) { + TRACE_CTOR(value_t, "const char *"); + new((amount_t *) data) amount_t(val); + type = AMOUNT; + } + value_t(const amount_t& val) { + TRACE_CTOR(value_t, "const amount_t&"); + new((amount_t *)data) amount_t(val); + type = AMOUNT; + } + value_t(const balance_t& val) : type(INTEGER) { + TRACE_CTOR(value_t, "const balance_t&"); + *this = val; + } + value_t(const balance_pair_t& val) : type(INTEGER) { + TRACE_CTOR(value_t, "const balance_pair_t&"); + *this = val; + } + value_t(xml::node_t * xml_node) : type(INTEGER) { // gets set in = + TRACE_CTOR(value_t, "xml::node_t *"); + *this = xml_node; + } + value_t(void * item) : type(INTEGER) { // gets set in = + TRACE_CTOR(value_t, "void *"); + *this = item; + } + value_t(sequence_t * seq) : type(INTEGER) { // gets set in = + TRACE_CTOR(value_t, "sequence_t *"); + *this = seq; + } + + ~value_t() { + TRACE_DTOR(value_t); + destroy(); + } + + void destroy(); + void simplify(); + + value_t& operator=(const value_t& val); + value_t& operator=(const bool val) { + if ((bool *) data != &val) { + destroy(); + *((bool *) data) = val; + type = BOOLEAN; + } + return *this; + } + value_t& operator=(const long val) { + if ((long *) data != &val) { + destroy(); + *((long *) data) = val; + type = INTEGER; + } + return *this; + } + value_t& operator=(const moment_t val) { + if ((moment_t *) data != &val) { + destroy(); + *((moment_t *) data) = val; + type = DATETIME; + } + return *this; + } + value_t& operator=(const unsigned long val) { + return *this = amount_t(val); + } + value_t& operator=(const double val) { + return *this = amount_t(val); + } + value_t& operator=(const string& val) { + return *this = amount_t(val); + } + value_t& operator=(const char * val) { + return *this = amount_t(val); + } + value_t& operator=(const amount_t& val) { + if (type == AMOUNT && + (amount_t *) data == &val) + return *this; + + if (val.realzero()) { + return *this = 0L; + } else { + destroy(); + new((amount_t *)data) amount_t(val); + type = AMOUNT; + } + return *this; + } + value_t& operator=(const balance_t& val) { + if (type == BALANCE && + (balance_t *) data == &val) + return *this; + + if (val.realzero()) { + return *this = 0L; + } + else if (val.amounts.size() == 1) { + return *this = (*val.amounts.begin()).second; + } + else { + destroy(); + new((balance_t *)data) balance_t(val); + type = BALANCE; + return *this; + } + } + value_t& operator=(const balance_pair_t& val) { + if (type == BALANCE_PAIR && + (balance_pair_t *) data == &val) + return *this; + + if (val.realzero()) { + return *this = 0L; + } + else if (! val.cost) { + return *this = val.quantity; + } + else { + destroy(); + new((balance_pair_t *)data) balance_pair_t(val); + type = BALANCE_PAIR; + return *this; + } + } + value_t& operator=(xml::node_t * xml_node) { + assert(xml_node); + if (type == XML_NODE && *(xml::node_t **) data == xml_node) + return *this; + + if (! xml_node) { + type = XML_NODE; + return *this = 0L; + } + else { + destroy(); + *(xml::node_t **)data = xml_node; + type = XML_NODE; + return *this; + } + } + value_t& operator=(void * item) { + assert(item); + if (type == POINTER && *(void **) data == item) + return *this; + + if (! item) { + type = POINTER; + return *this = 0L; + } + else { + destroy(); + *(void **)data = item; + type = POINTER; + return *this; + } + } + value_t& operator=(sequence_t * seq) { + assert(seq); + if (type == SEQUENCE && *(sequence_t **) data == seq) + return *this; + + if (! seq) { + type = SEQUENCE; + return *this = 0L; + } + else { + destroy(); + *(sequence_t **)data = seq; + type = SEQUENCE; + return *this; + } + } + + value_t& set_string(const string& str = "") { + if (type != STRING) { + destroy(); + *(string **) data = new string(str); + type = STRING; + } else { + **(string **) data = str; + } + return *this; + } + + bool to_boolean() const; + long to_integer() const; + moment_t to_datetime() const; + amount_t to_amount() const; + balance_t to_balance() const; + balance_pair_t to_balance_pair() const; + string to_string() const; + xml::node_t * to_xml_node() const; + void * to_pointer() const; + sequence_t * to_sequence() const; + + value_t& operator[](const int index) { + sequence_t * seq = to_sequence(); + assert(seq); + return (*seq)[index]; + } + + void push_back(const value_t& val) { + sequence_t * seq = to_sequence(); + assert(seq); + return seq->push_back(val); + } + + std::size_t size() const { + sequence_t * seq = to_sequence(); + assert(seq); + return seq->size(); + } + + 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); + + template + value_t& operator+=(const T& val) { + return *this += value_t(val); + } + template + value_t& operator-=(const T& val) { + return *this -= value_t(val); + } + template + value_t& operator*=(const T& val) { + return *this *= value_t(val); + } + template + value_t& operator/=(const T& val) { + return *this /= value_t(val); + } + + value_t operator+(const value_t& val) { + value_t temp(*this); + temp += val; + return temp; + } + value_t operator-(const value_t& val) { + value_t temp(*this); + temp -= val; + return temp; + } + value_t operator*(const value_t& val) { + value_t temp(*this); + temp *= val; + return temp; + } + value_t operator/(const value_t& val) { + value_t temp(*this); + temp /= val; + return temp; + } + + template + value_t operator+(const T& val) { + return *this + value_t(val); + } + template + value_t operator-(const T& val) { + return *this - value_t(val); + } + template + value_t operator*(const T& val) { + return *this * value_t(val); + } + template + value_t operator/(const T& val) { + return *this / value_t(val); + } + + bool operator<(const value_t& val); + bool operator<=(const value_t& val); + bool operator>(const value_t& val); + bool operator>=(const value_t& val); + bool operator==(const value_t& val); + bool operator!=(const value_t& val) { + return ! (*this == val); + } + + template + bool operator<(const T& val) { + return *this < value_t(val); + } + template + bool operator<=(const T& val) { + return *this <= value_t(val); + } + template + bool operator>(const T& val) { + return *this > value_t(val); + } + template + bool operator>=(const T& val) { + return *this >= value_t(val); + } + template + bool operator==(const T& val) { + return *this == value_t(val); + } + template + bool operator!=(const T& val) { + return ! (*this == val); + } + + template + operator T() const; + + void in_place_negate(); + value_t negate() const { + value_t temp = *this; + temp.in_place_negate(); + return temp; + } + value_t operator-() const { + return negate(); + } + + bool realzero() const { + switch (type) { + case BOOLEAN: + return ! *((bool *) data); + case INTEGER: + return *((long *) data) == 0; + case DATETIME: + return ! is_valid_moment(*((moment_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(); + case STRING: + return ((string *) data)->empty(); + case XML_NODE: + case POINTER: + case SEQUENCE: + return *(void **) data == NULL; + + default: + assert(0); + break; + } + assert(0); + return 0; + } + + void in_place_abs(); + value_t abs() const; + void in_place_cast(type_t cast_type); + value_t cost() const; + value_t price() const; + value_t date() const; + + value_t cast(type_t cast_type) const { + value_t temp(*this); + temp.in_place_cast(cast_type); + return temp; + } + + 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 moment_t& moment) const; + void in_place_reduce(); + + value_t reduce() const { + value_t temp(*this); + temp.in_place_reduce(); + return temp; + } + + value_t round() const; + value_t unround() const; + + void write(std::ostream& out, const int first_width, + const int latter_width = -1) const; +}; + +#define DEFINE_VALUE_OPERATORS(T, OP) \ +inline value_t operator OP(const T& val, const value_t& obj) { \ + return value_t(val) OP obj; \ +} + +DEFINE_VALUE_OPERATORS(bool, ==) +DEFINE_VALUE_OPERATORS(bool, !=) + +DEFINE_VALUE_OPERATORS(long, +) +DEFINE_VALUE_OPERATORS(long, -) +DEFINE_VALUE_OPERATORS(long, *) +DEFINE_VALUE_OPERATORS(long, /) +DEFINE_VALUE_OPERATORS(long, <) +DEFINE_VALUE_OPERATORS(long, <=) +DEFINE_VALUE_OPERATORS(long, >) +DEFINE_VALUE_OPERATORS(long, >=) +DEFINE_VALUE_OPERATORS(long, ==) +DEFINE_VALUE_OPERATORS(long, !=) + +DEFINE_VALUE_OPERATORS(amount_t, +) +DEFINE_VALUE_OPERATORS(amount_t, -) +DEFINE_VALUE_OPERATORS(amount_t, *) +DEFINE_VALUE_OPERATORS(amount_t, /) +DEFINE_VALUE_OPERATORS(amount_t, <) +DEFINE_VALUE_OPERATORS(amount_t, <=) +DEFINE_VALUE_OPERATORS(amount_t, >) +DEFINE_VALUE_OPERATORS(amount_t, >=) +DEFINE_VALUE_OPERATORS(amount_t, ==) +DEFINE_VALUE_OPERATORS(amount_t, !=) + +DEFINE_VALUE_OPERATORS(balance_t, +) +DEFINE_VALUE_OPERATORS(balance_t, -) +DEFINE_VALUE_OPERATORS(balance_t, *) +DEFINE_VALUE_OPERATORS(balance_t, /) +DEFINE_VALUE_OPERATORS(balance_t, <) +DEFINE_VALUE_OPERATORS(balance_t, <=) +DEFINE_VALUE_OPERATORS(balance_t, >) +DEFINE_VALUE_OPERATORS(balance_t, >=) +DEFINE_VALUE_OPERATORS(balance_t, ==) +DEFINE_VALUE_OPERATORS(balance_t, !=) + +DEFINE_VALUE_OPERATORS(balance_pair_t, +) +DEFINE_VALUE_OPERATORS(balance_pair_t, -) +DEFINE_VALUE_OPERATORS(balance_pair_t, *) +DEFINE_VALUE_OPERATORS(balance_pair_t, /) +DEFINE_VALUE_OPERATORS(balance_pair_t, <) +DEFINE_VALUE_OPERATORS(balance_pair_t, <=) +DEFINE_VALUE_OPERATORS(balance_pair_t, >) +DEFINE_VALUE_OPERATORS(balance_pair_t, >=) +DEFINE_VALUE_OPERATORS(balance_pair_t, ==) +DEFINE_VALUE_OPERATORS(balance_pair_t, !=) + +template +value_t::operator T() const +{ + switch (type) { + case BOOLEAN: + return *(bool *) data; + case INTEGER: + return *(long *) data; + case DATETIME: + return *(moment_t *) data; + case AMOUNT: + return *(amount_t *) data; + case BALANCE: + return *(balance_t *) data; + case STRING: + return **(string **) data; + case XML_NODE: + return *(xml::node_t **) data; + case POINTER: + return *(void **) data; + case SEQUENCE: + return *(sequence_t **) data; + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> value_t::operator bool() const; +template <> value_t::operator long() const; +template <> value_t::operator moment_t() const; +template <> value_t::operator double() const; +template <> value_t::operator string() const; + +std::ostream& operator<<(std::ostream& out, const value_t& val); + +#if 0 +class value_context : public error_context +{ + value_t * bal; + public: + value_context(const value_t& _bal, + const string& desc = "") throw(); + virtual ~value_context() throw(); + + virtual void describe(std::ostream& out) const throw(); +}; +#endif + +DECLARE_EXCEPTION(value_exception); + +} // namespace ledger + +#endif // _VALUE_H diff --git a/src/xml.cc b/src/xml.cc new file mode 100644 index 00000000..448b62b7 --- /dev/null +++ b/src/xml.cc @@ -0,0 +1,550 @@ +#include "xml.h" +#include "journal.h" + +namespace ledger { +namespace xml { + +const std::size_t document_t::ledger_builtins_size = 12; +const char * document_t::ledger_builtins[] = { + "account", + "account-path", + "amount", + "code", + "commodity", + "entries", + "entry", + "journal", + "name", + "note", + "payee", + "transaction" +}; + +document_t::document_t(node_t * _top) + : stub(this), top(_top ? _top : &stub) { + TRACE_CTOR(xml::document_t, "node_t *, const char **, const int"); +} + +document_t::~document_t() +{ + TRACE_DTOR(xml::document_t); + if (top && top != &stub) + delete top; +} + +void document_t::set_top(node_t * _top) +{ + if (top && top != &stub) + delete top; + top = _top; +} + +int document_t::register_name(const string& name) +{ + int index = lookup_name_id(name); + if (index != -1) + return index; + + names.push_back(name); + index = names.size() - 1; + + DEBUG_("xml.lookup", this << " Inserting name: " << names.back()); + + std::pair result = + names_index.insert(names_pair(names.back(), index)); + assert(result.second); + + return index + 1000; +} + +int document_t::lookup_name_id(const string& name) const +{ + int id; + if ((id = lookup_builtin_id(name)) != -1) + return id; + + DEBUG_("xml.lookup", this << " Finding name: " << name); + + names_map::const_iterator i = names_index.find(name); + if (i != names_index.end()) + return (*i).second + 1000; + + return -1; +} + +int document_t::lookup_builtin_id(const string& name) +{ + int first = 0; + int last = (int)ledger_builtins_size; + + while (first <= last) { + int mid = (first + last) / 2; // compute mid point. + + int result; + if ((result = (int)name[0] - (int)ledger_builtins[mid][0]) == 0) + result = std::strcmp(name.c_str(), ledger_builtins[mid]); + + 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 mid + 10; + } + + return -1; +} + +const char * document_t::lookup_name(int id) const +{ + if (id < 1000) { + switch (id) { + case CURRENT: + return "CURRENT"; + case PARENT: + return "PARENT"; + case ROOT: + return "ROOT"; + case ALL: + return "ALL"; + default: + assert(id >= 10); + return ledger_builtins[id - 10]; + } + } else { + return names[id - 1000].c_str(); + } +} + +void document_t::write(std::ostream& out) const +{ + if (top) { + out << "\n"; + top->write(out); + } +} + +#ifndef THREADSAFE +document_t * node_t::document; +#endif + +node_t::node_t(document_t * _document, parent_node_t * _parent, + unsigned int _flags) + : name_id(0), parent(_parent), next(NULL), prev(NULL), + flags(_flags), attrs(NULL) +{ + TRACE_CTOR(node_t, "document_t *, node_t *"); + document = _document; + if (document && ! document->top) + document->set_top(this); + if (parent) + parent->add_child(this); +} + +void node_t::extract() +{ + if (prev) + prev->next = next; + + if (parent) { + if (parent->_children == this) + parent->_children = next; + + if (parent->_last_child == this) + parent->_last_child = prev; + + parent = NULL; + } + + if (next) + next->prev = prev; + + next = NULL; + prev = NULL; +} + +const char * node_t::name() const +{ + return document->lookup_name(name_id); +} + +int node_t::set_name(const char * _name) +{ + name_id = document->register_name(_name); + return name_id; +} + +node_t * node_t::lookup_child(const char * _name) const +{ + int id = document->lookup_name_id(_name); + return lookup_child(id); +} + +node_t * node_t::lookup_child(const string& _name) const +{ + int id = document->lookup_name_id(_name); + return lookup_child(id); +} + +void parent_node_t::clear() +{ + node_t * child = _children; + while (child) { + node_t * tnext = child->next; + delete child; + child = tnext; + } +} + +void parent_node_t::add_child(node_t * node) +{ + // It is important that this node is not called before children(), + // otherwise, this node will not get auto-populated. + if (_children == NULL) { + assert(_last_child == NULL); + _children = node; + node->prev = NULL; + } else { + assert(_last_child != NULL); + _last_child->next = node; + node->prev = _last_child; + } + + node->parent = this; + + while (node->next) { + node_t * next_node = node->next; + assert(next_node->prev == node); + next_node->parent = this; + node = next_node; + } + + _last_child = node; +} + +void parent_node_t::write(std::ostream& out, int depth) const +{ + for (int i = 0; i < depth; i++) out << " "; + out << '<' << name() << ">\n"; + + for (node_t * child = children(); child; child = child->next) + child->write(out, depth + 1); + + for (int i = 0; i < depth; i++) out << " "; + out << "\n"; +} + +void terminal_node_t::write(std::ostream& out, int depth) const +{ + for (int i = 0; i < depth; i++) out << " "; + + if (data.empty()) { + out << '<' << name() << " />\n"; + } else { + out << '<' << name() << ">" + << text() + << "\n"; + } +} + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +template +inline T * create_node(document_t::parser_t * parser) +{ + T * node = new T(parser->document, parser->node_stack.empty() ? + NULL : parser->node_stack.front()); + + node->set_name(parser->pending); + node->attrs = parser->pending_attrs; + + parser->pending = NULL; + parser->pending_attrs = NULL; + + return node; +} + +static void startElement(void *userData, const char *name, const char **attrs) +{ + document_t::parser_t * parser = static_cast(userData); + + DEBUG_("xml.parse", "startElement(" << name << ")"); + + if (parser->pending) { + parent_node_t * node = create_node(parser); + if (parser->node_stack.empty()) + parser->document->top = node; + parser->node_stack.push_front(node); + } + + parser->pending = name; + + if (attrs) { + for (const char ** p = attrs; *p; p += 2) { + if (! parser->pending_attrs) + parser->pending_attrs = new node_t::attrs_map; + + std::pair result + = parser->pending_attrs->insert(node_t::attrs_pair(*p, *(p + 1))); + assert(result.second); + } + } +} + +static void endElement(void *userData, const char *name) +{ + document_t::parser_t * parser = static_cast(userData); + + DEBUG_("xml.parse", "endElement(" << name << ")"); + + if (parser->pending) { + terminal_node_t * node = create_node(parser); + if (parser->node_stack.empty()) { + parser->document->top = node; + return; + } + } + else if (! parser->handled_data) { + assert(! parser->node_stack.empty()); + parser->node_stack.pop_front(); + } + else { + parser->handled_data = false; + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + document_t::parser_t * parser = static_cast(userData); + + DEBUG_("xml.parse", "dataHandler(" << string(s, len) << ")"); + + bool all_whitespace = true; + for (int i = 0; i < len; i++) { + if (! std::isspace(s[i])) { + all_whitespace = false; + break; + } + } + + // jww (2006-09-28): I currently do not support text nodes within a + // node that has children. + + if (! all_whitespace) { + terminal_node_t * node = create_node(parser); + + node->set_text(string(s, len)); + parser->handled_data = true; + + if (parser->node_stack.empty()) { + parser->document->top = node; + return; + } + } +} + +bool document_t::parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, " doc(new document_t); + + document = doc.get(); + + parser = XML_ParserCreate(NULL); + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); + + char buf[BUFSIZ]; + while (! in.eof()) { + in.getline(buf, BUFSIZ - 1); + std::strcat(buf, "\n"); + bool result; + try { + result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); + } + catch (const std::exception& err) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + XML_ParserFree(parser); + throw_(parse_exception, err.what()); + } + + if (! have_error.empty()) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; +#if 0 + // jww (2007-04-26): What is this doing?? + parse_exception err(have_error); + std::cerr << "Error: " << err.what() << std::endl; +#endif + have_error = ""; + } + + if (! result) { + //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * err = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw_(parse_exception, err); + } + } + + XML_ParserFree(parser); + + document = NULL; + return doc.release(); +} + +node_t * commodity_node_t::children() const +{ + // jww (2007-04-19): Need to report the commodity and its details + return NULL; +} + +node_t * amount_node_t::children() const +{ + // jww (2007-04-19): Need to report the quantity and commodity + return NULL; +} + +node_t * transaction_node_t::children() const +{ + return parent_node_t::children(); +} + +node_t * transaction_node_t::lookup_child(int _name_id) const +{ + switch (_name_id) { + case document_t::PAYEE: + payee_virtual_node = new terminal_node_t(document); + payee_virtual_node->set_text(transaction->entry->payee); + return payee_virtual_node; + + case document_t::ACCOUNT: + return new account_node_t(document, transaction->account, + const_cast(this)); + } + return NULL; +} + +value_t transaction_node_t::to_value() const +{ + return transaction->amount; +} + +node_t * entry_node_t::children() const +{ + if (! _children) + for (transactions_list::iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + new transaction_node_t(document, *i, const_cast(this)); + + return parent_node_t::children(); +} + +node_t * entry_node_t::lookup_child(int _name_id) const +{ + switch (_name_id) { + case document_t::CODE: + // jww (2007-04-20): I have to save this and then delete it later + terminal_node_t * code_node = + new terminal_node_t(document, const_cast(this)); + code_node->set_name(document_t::CODE); + code_node->set_text(entry->code); + return code_node; + + case document_t::PAYEE: + // jww (2007-04-20): I have to save this and then delete it later + terminal_node_t * payee_node = + new terminal_node_t(document, const_cast(this)); + payee_node->set_name(document_t::PAYEE); + payee_node->set_text(entry->payee); + return payee_node; + } + return NULL; +} + +node_t * account_node_t::children() const +{ + if (! _children) { + if (! account->name.empty()) { + terminal_node_t * name_node = + new terminal_node_t(document, const_cast(this)); + name_node->set_name(document_t::NAME); + name_node->set_text(account->name); + } + + if (! account->note.empty()) { + terminal_node_t * note_node = + new terminal_node_t(document, const_cast(this)); + note_node->set_name(document_t::NOTE); + note_node->set_text(account->note); + } + + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); + i++) + new account_node_t(document, (*i).second, const_cast(this)); + } + return parent_node_t::children(); +} + +node_t * journal_node_t::children() const +{ + if (! _children) { +#if 0 + account_node_t * master_account = + new account_node_t(document, journal->master, const_cast(this)); +#endif + + parent_node_t * entries = + new parent_node_t(document, const_cast(this)); + entries->set_name(document_t::ENTRIES); + + for (entries_list::iterator i = journal->entries.begin(); + i != journal->entries.end(); + i++) + new entry_node_t(document, *i, const_cast(this)); + } + return parent_node_t::children(); +} + +#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +void output_xml_string(std::ostream& out, const string& str) +{ + for (const char * s = str.c_str(); *s; s++) { + switch (*s) { + case '<': + out << "<"; + break; + case '>': + out << "&rt;"; + break; + case '&': + out << "&"; + break; + default: + out << *s; + break; + } + } +} + +} // namespace xml +} // namespace ledger diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 00000000..6a600786 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,423 @@ +#ifndef _XML_H +#define _XML_H + +#include "value.h" +#include "parser.h" + +namespace ledger { + +class transaction_t; +class entry_t; +class account_t; +class journal_t; + +namespace xml { + +#define XML_NODE_IS_PARENT 0x1 + +DECLARE_EXCEPTION(conversion_exception); + +class parent_node_t; +class document_t; + +class node_t +{ +public: + unsigned int name_id; +#ifdef THREADSAFE + document_t * document; +#else + static document_t * document; +#endif + parent_node_t * parent; + node_t * next; + node_t * prev; + unsigned int flags; + + typedef std::map attrs_map; + typedef std::pair attrs_pair; + + attrs_map * attrs; + + node_t(document_t * _document, parent_node_t * _parent = NULL, + unsigned int _flags = 0); + + virtual ~node_t() { + TRACE_DTOR(node_t); + if (parent) extract(); + if (attrs) delete attrs; + } + + void extract(); // extract this node from its parent's child list + + virtual const char * text() const { + assert(0); + return NULL; + } + + const char * name() const; + int set_name(const char * _name); + int set_name(int _name_id) { + name_id = _name_id; + return name_id; + } + + void set_attr(const char * n, const char * v) { + if (! attrs) + attrs = new attrs_map; + std::pair result = + attrs->insert(attrs_pair(n, v)); + assert(result.second); + } + const char * get_attr(const char * n) { + if (attrs) { + attrs_map::iterator i = attrs->find(n); + if (i != attrs->end()) + return (*i).second.c_str(); + } + return NULL; + } + + node_t * lookup_child(const char * _name) const; + node_t * lookup_child(const string& _name) const; + virtual node_t * lookup_child(int /* _name_id */) const { + return NULL; + } + + virtual value_t to_value() const { + throw_(conversion_exception, "Cannot convert node to a value"); + } + + virtual void write(std::ostream& out, int depth = 0) const = 0; + +private: + node_t(const node_t&); + node_t& operator=(const node_t&); +}; + +class parent_node_t : public node_t +{ +public: + mutable node_t * _children; + mutable node_t * _last_child; + + parent_node_t(document_t * _document, parent_node_t * _parent = NULL) + : node_t(_document, _parent, XML_NODE_IS_PARENT), + _children(NULL), _last_child(NULL) + { + TRACE_CTOR(parent_node_t, "document_t *, parent_node_t *"); + } + virtual ~parent_node_t() { + TRACE_DTOR(parent_node_t); + if (_children) clear(); + } + + virtual void clear(); // clear out all child nodes + virtual node_t * children() const { + return _children; + } + virtual node_t * last_child() { + if (! _children) + children(); + return _last_child; + } + virtual void add_child(node_t * node); + + void write(std::ostream& out, int depth = 0) const; + +private: + parent_node_t(const parent_node_t&); + parent_node_t& operator=(const parent_node_t&); +}; + +class terminal_node_t : public node_t +{ + string data; + +public: + terminal_node_t(document_t * _document, parent_node_t * _parent = NULL) + : node_t(_document, _parent) + { + TRACE_CTOR(terminal_node_t, "document_t *, parent_node_t *"); + } + virtual ~terminal_node_t() { + TRACE_DTOR(terminal_node_t); + } + + virtual const char * text() const { + return data.c_str(); + } + virtual void set_text(const char * _data) { + data = _data; + } + virtual void set_text(const string& _data) { + data = _data; + } + + virtual value_t to_value() const { + return text(); + } + + void write(std::ostream& out, int depth = 0) const; + +private: + terminal_node_t(const node_t&); + terminal_node_t& operator=(const node_t&); +}; + +class document_t +{ + static const char * ledger_builtins[]; + static const std::size_t ledger_builtins_size; + +public: + enum ledger_builtins_t { + ACCOUNT = 10, + ACCOUNT_PATH, + AMOUNT, + CODE, + COMMODITY, + ENTRIES, + ENTRY, + JOURNAL, + NAME, + NOTE, + PAYEE, + TRANSACTION + }; + +private: + typedef std::vector names_array; + + names_array names; + + typedef std::map names_map; + typedef std::pair names_pair; + + names_map names_index; + + terminal_node_t stub; + + public: + node_t * top; + + // Ids 0-9 are reserved. 10-999 are for "builtin" names. 1000+ are + // for dynamically registered names. + enum special_names_t { + CURRENT, PARENT, ROOT, ALL + }; + + document_t(node_t * _top = NULL); + ~document_t(); + + void set_top(node_t * _top); + + int register_name(const string& name); + int lookup_name_id(const string& name) const; + static int lookup_builtin_id(const string& name); + const char * lookup_name(int id) const; + + void write(std::ostream& out) const; + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + class parser_t + { + public: + document_t * document; + XML_Parser parser; + string have_error; + const char * pending; + node_t::attrs_map * pending_attrs; + bool handled_data; + + std::list node_stack; + + parser_t() : document(NULL), pending(NULL), pending_attrs(NULL), + handled_data(false) {} + virtual ~parser_t() {} + + virtual bool test(std::istream& in) const; + virtual document_t * parse(std::istream& in); + }; +#endif +}; + +#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, + journal_t * journal, + account_t * master = NULL, + const string * original_file = NULL); +}; + +DECLARE_EXCEPTION(parse_exception); + +#endif + +class commodity_node_t : public parent_node_t +{ +public: + commodity_t * commodity; + + commodity_node_t(document_t * _document, + commodity_t * _commodity, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), commodity(_commodity) { + TRACE_CTOR(commodity_node_t, "document_t *, commodity_t *, parent_node_t *"); + set_name(document_t::COMMODITY); + } + virtual ~commodity_node_t() { + TRACE_DTOR(commodity_node_t); + } + + virtual node_t * children() const; +}; + +class amount_node_t : public parent_node_t +{ +public: + amount_t * amount; + + amount_node_t(document_t * _document, + amount_t * _amount, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), amount(_amount) { + TRACE_CTOR(amount_node_t, "document_t *, amount_t *, parent_node_t *"); + set_name(document_t::AMOUNT); + } + virtual ~amount_node_t() { + TRACE_DTOR(amount_node_t); + } + + virtual node_t * children() const; + + virtual value_t to_value() const { + return *amount; + } +}; + +class transaction_node_t : public parent_node_t +{ + mutable terminal_node_t * payee_virtual_node; + +public: + transaction_t * transaction; + + transaction_node_t(document_t * _document, + transaction_t * _transaction, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), payee_virtual_node(NULL), + transaction(_transaction) { + TRACE_CTOR(transaction_node_t, "document_t *, transaction_t *, parent_node_t *"); + set_name(document_t::TRANSACTION); + } + virtual ~transaction_node_t() { + TRACE_DTOR(transaction_node_t); + if (payee_virtual_node) + delete payee_virtual_node; + } + + virtual node_t * children() const; + virtual node_t * lookup_child(int _name_id) const; + virtual value_t to_value() const; +}; + +class entry_node_t : public parent_node_t +{ + entry_t * entry; + +public: + entry_node_t(document_t * _document, entry_t * _entry, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), entry(_entry) { + TRACE_CTOR(entry_node_t, "document_t *, entry_t *, parent_node_t *"); + set_name(document_t::ENTRY); + } + virtual ~entry_node_t() { + TRACE_DTOR(entry_node_t); + } + + virtual node_t * children() const; + virtual node_t * lookup_child(int _name_id) const; + + friend class transaction_node_t; +}; + +class account_node_t : public parent_node_t +{ + account_t * account; + +public: + account_node_t(document_t * _document, account_t * _account, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), account(_account) { + TRACE_CTOR(account_node_t, "document_t *, account_t *, parent_node_t *"); + set_name(document_t::ACCOUNT); + } + virtual ~account_node_t() { + TRACE_DTOR(account_node_t); + } + + virtual node_t * children() const; +}; + +class journal_node_t : public parent_node_t +{ + journal_t * journal; + +public: + journal_node_t(document_t * _document, journal_t * _journal, + parent_node_t * _parent = NULL) + : parent_node_t(_document, _parent), journal(_journal) { + TRACE_CTOR(journal_node_t, "document_t *, journal_t *, parent_node_t *"); + set_name(document_t::JOURNAL); + } + virtual ~journal_node_t() { + TRACE_DTOR(journal_node_t); + } + + virtual node_t * children() const; + + friend class transaction_node_t; +}; + +template +inline parent_node_t * wrap_node(document_t * doc, T * item, + void * parent_node = NULL) { + assert(0); + return NULL; +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, transaction_t * xact, + void * parent_node) { + return new transaction_node_t(doc, xact, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, entry_t * entry, + void * parent_node) { + return new entry_node_t(doc, entry, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, account_t * account, + void * parent_node) { + return new account_node_t(doc, account, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, journal_t * journal, + void * parent_node) { + return new journal_node_t(doc, journal, (parent_node_t *)parent_node); +} + +} // namespace xml +} // namespace ledger + +#endif // _XML_H diff --git a/src/xmlparse.cc b/src/xmlparse.cc new file mode 100644 index 00000000..5dfcdbaa --- /dev/null +++ b/src/xmlparse.cc @@ -0,0 +1,467 @@ +#include "xml.h" +#include "journal.h" + +namespace ledger { +namespace xml { + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +static XML_Parser current_parser; +static unsigned int count; + +static journal_t * curr_journal; +static entry_t * curr_entry; +static commodity_t * curr_comm; +static string comm_flags; + +static transaction_t::state_t curr_state; + +static string data; +static bool ignore; +static string have_error; + +static void startElement(void *userData, const char *name, const char **attrs) +{ + if (ignore) + return; + + if (std::strcmp(name, "entry") == 0) { + assert(! curr_entry); + curr_entry = new entry_t; + curr_state = transaction_t::UNCLEARED; + } + else if (std::strcmp(name, "transaction") == 0) { + assert(curr_entry); + curr_entry->add_transaction(new transaction_t); + if (curr_state != transaction_t::UNCLEARED) + curr_entry->transactions.back()->state = curr_state; + } + else if (std::strcmp(name, "commodity") == 0) { + if (string(attrs[0]) == "flags") + comm_flags = attrs[1]; + } + else if (std::strcmp(name, "total") == 0) { + ignore = true; + } +} + +static void endElement(void *userData, const char *name) +{ + if (ignore) { + if (std::strcmp(name, "total") == 0) + ignore = false; + return; + } + + if (std::strcmp(name, "entry") == 0) { + assert(curr_entry); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + account_t * acct = curr_journal->find_account(""); + curr_entry->add_transaction(new transaction_t(acct)); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + delete curr_entry; + have_error = "Entry cannot be balanced"; + } + } + curr_entry = NULL; + } + else if (std::strcmp(name, "en:date") == 0) { + curr_entry->_date = parse_datetime(data); + } + else if (std::strcmp(name, "en:date_eff") == 0) { + curr_entry->_date_eff = parse_datetime(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; + } + else if (std::strcmp(name, "en:pending") == 0) { + curr_state = transaction_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); + } + else if (std::strcmp(name, "tr:cleared") == 0) { + curr_entry->transactions.back()->state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "tr:pending") == 0) { + curr_entry->transactions.back()->state = transaction_t::PENDING; + } + else if (std::strcmp(name, "tr:virtual") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; + } + else if (std::strcmp(name, "tr:generated") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; + } + else if (std::strcmp(name, "symbol") == 0) { + assert(! curr_comm); + curr_comm = commodity_t::find_or_create(data); + assert(curr_comm); + curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); + if (! comm_flags.empty()) { + 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; + case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; + case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; + } + } + } + } +#if 0 + // jww (2006-03-02): !!! + else if (std::strcmp(name, "price") == 0) { + assert(curr_comm); + amount_t * price = new amount_t(data); + std::ostringstream symstr; + symstr << curr_comm->symbol << " {" << *price << "}"; + commodity_t * priced_comm = + commodity_t::find_commodity(symstr.str(), true); + priced_comm->price = price; + priced_comm->base = curr_comm; + curr_comm = priced_comm; + } +#endif + else if (std::strcmp(name, "quantity") == 0) { + curr_entry->transactions.back()->amount.parse(data); + if (curr_comm) { + 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_comm = NULL; + } + } + else if (std::strcmp(name, "tr:amount") == 0) { + curr_comm = NULL; + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + if (! ignore) + data = string(s, len); +} + +bool xml_parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, "\n"; + + commodity_t& c = amount.commodity(); + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; +#if 0 + // jww (2006-03-02): !!! + if (c.price) { + out << "" << c.base->symbol << "\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "\n"; + xml_write_amount(out, *c.price, depth + 6); + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "\n"; + } else { + out << "" << c.symbol << "\n"; + } +#endif + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << ""; + out << amount.quantity_string() << "\n"; + + for (int i = 0; i < depth; i++) out << ' '; + out << "\n"; +} + +void xml_write_value(std::ostream& out, const value_t& value, + const int depth = 0) +{ + balance_t * bal = NULL; + + for (int i = 0; i < depth; i++) out << ' '; + out << "\n"; + + switch (value.type) { + case value_t::BOOLEAN: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "" << *((bool *) value.data) << "\n"; + break; + + case value_t::INTEGER: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "" << *((long *) value.data) << "\n"; + break; + + case value_t::AMOUNT: + xml_write_amount(out, *((amount_t *) value.data), depth + 2); + 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; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + + for (amounts_map::const_iterator i = bal->amounts.begin(); + i != bal->amounts.end(); + i++) + xml_write_amount(out, (*i).second, depth + 4); + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "\n"; + break; + + default: + assert(0); + break; + } + + for (int i = 0; i < depth; i++) out << ' '; + out << "\n"; +} + +void output_xml_string(std::ostream& out, const string& str) +{ + for (const char * s = str.c_str(); *s; s++) { + switch (*s) { + case '<': + out << "<"; + break; + case '>': + out << "&rt;"; + break; + case '&': + out << "&"; + break; + default: + out << *s; + break; + } + } +} + +void format_xml_entries::format_last_entry() +{ + output_stream << " \n" + << " " << last_entry->_date.to_string("%Y/%m/%d") + << "\n"; + + if (last_entry->_date_eff) + output_stream << " " + << last_entry->_date_eff.to_string("%Y/%m/%d") + << "\n"; + + if (! last_entry->code.empty()) { + output_stream << " "; + output_xml_string(output_stream, last_entry->code); + output_stream << "\n"; + } + + if (! last_entry->payee.empty()) { + output_stream << " "; + output_xml_string(output_stream, last_entry->payee); + output_stream << "\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) { + if (first) { + output_stream << " \n"; + first = false; + } + + output_stream << " \n"; + + if ((*i)->_date) + output_stream << " " + << (*i)->_date.to_string("%Y/%m/%d") + << "\n"; + + if ((*i)->_date_eff) + output_stream << " " + << (*i)->_date_eff.to_string("%Y/%m/%d") + << "\n"; + + if ((*i)->state == transaction_t::CLEARED) + output_stream << " \n"; + else if ((*i)->state == transaction_t::PENDING) + output_stream << " \n"; + + if ((*i)->flags & TRANSACTION_VIRTUAL) + output_stream << " \n"; + if ((*i)->flags & TRANSACTION_AUTO) + output_stream << " \n"; + + if ((*i)->account) { + string name = (*i)->account->fullname(); + if (name == "") + name = "[TOTAL]"; + else if (name == "") + name = "[UNKNOWN]"; + + output_stream << " "; + output_xml_string(output_stream, name); + output_stream << "\n"; + } + + output_stream << " \n"; + if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND) + xml_write_value(output_stream, + transaction_xdata_(**i).value, 10); + else + xml_write_value(output_stream, value_t((*i)->amount), 10); + output_stream << " \n"; + + if ((*i)->cost) { + output_stream << " \n"; + xml_write_value(output_stream, value_t(*(*i)->cost), 10); + output_stream << " \n"; + } + + if (! (*i)->note.empty()) { + output_stream << " "; + output_xml_string(output_stream, (*i)->note); + output_stream << "\n"; + } + + if (show_totals) { + output_stream << " \n"; + xml_write_value(output_stream, transaction_xdata_(**i).total, 10); + output_stream << " \n"; + } + + output_stream << " \n"; + + transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; + } + } + + if (! first) + output_stream << " \n"; + + output_stream << " \n"; +} +#endif + +} // namespace xml +} // namespace ledger diff --git a/src/xpath.cc b/src/xpath.cc new file mode 100644 index 00000000..6eb30d48 --- /dev/null +++ b/src/xpath.cc @@ -0,0 +1,2414 @@ +#include "xpath.h" +#include "parser.h" + +namespace ledger { +namespace xml { + +#ifndef THREADSAFE +xpath_t::token_t * xpath_t::lookahead = NULL; +#endif + +void xpath_t::initialize() +{ + lookahead = new xpath_t::token_t; +} + +void xpath_t::shutdown() +{ + delete lookahead; + lookahead = NULL; +} + +void xpath_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 == '.'); + + switch (buf[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; + case 'f': + if (std::strcmp(buf, "false") == 0) { + kind = VALUE; + value = false; + } + break; + 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; + case 't': + if (std::strcmp(buf, "true") == 0) { + kind = VALUE; + value = true; + } + break; + case 'u': + if (std::strcmp(buf, "union") == 0) + kind = KW_UNION; + break; + } + + if (kind == IDENT) + value.set_string(buf); +} + +void xpath_t::token_t::next(std::istream& in, unsigned short flags) +{ + 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; + + if (! (flags & XPATH_PARSE_RELAXED) && + (std::isalpha(c) || c == '_')) { + parse_ident(in); + return; + } + + switch (c) { + case '@': + in.get(c); + kind = AT_SYM; + break; +#if 0 + case '$': + in.get(c); + kind = DOLLAR; + break; +#endif + + case '(': + in.get(c); + kind = LPAREN; + break; + case ')': + in.get(c); + kind = RPAREN; + break; + + case '[': { + in.get(c); + if (flags & XPATH_PARSE_ALLOW_DATE) { + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != ']'); + if (c != ']') + unexpected(c, ']'); + in.get(c); + length++; + interval_t timespan(buf); + kind = VALUE; + value = timespan.next(); + } else { + kind = LBRACKET; + } + break; + } + + case ']': { + in.get(c); + kind = RBRACKET; + break; + } + + case '"': { + in.get(c); + char buf[4096]; + READ_INTO_(in, buf, 4095, c, length, c != '"'); + if (c != '"') + unexpected(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 != '}') + unexpected(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; + } +#if 0 + else if (c == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NMATCH; + length = 2; + break; + } +#endif + kind = EXCLAM; + break; + + case '-': + in.get(c); + kind = MINUS; + break; + case '+': + in.get(c); + kind = PLUS; + break; + + case '*': + in.get(c); + if (in.peek() == '*') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = POWER; + length = 2; + break; + } + kind = STAR; + break; + + case '/': + in.get(c); +#if 0 + if (flags & XPATH_PARSE_REGEXP) { + char buf[1024]; + READ_INTO_(in, buf, 1023, c, length, c != '/'); + in.get(c); + if (c != '/') + unexpected(c, '/'); + kind = REGEXP; + value.set_string(buf); + break; + } +#endif + kind = SLASH; + break; + + case '=': + in.get(c); +#if 0 + if (in.peek() == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = MATCH; + length = 2; + break; + } +#endif + 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 = AMPER; + break; + case '|': + in.get(c); + kind = PIPE; + break; + case '?': + in.get(c); + kind = QUESTION; + break; + case ':': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = ASSIGN; + length = 2; + break; + } + kind = COLON; + break; + case ',': + in.get(c); + kind = COMMA; + break; +#if 0 + case '%': + in.get(c); + kind = PERCENT; + break; +#endif + + case '.': + in.get(c); + c = in.peek(); + if (c == '.') { + in.get(c); + length++; + kind = DOTDOT; + break; + } + else if (! std::isdigit(c)) { + kind = DOT; + break; + } + in.unget(); // put the first '.' back + // fall through... + + default: + if (! (flags & XPATH_PARSE_RELAXED)) { + kind = UNKNOWN; + } else { + 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. + try { + pos = (long)in.tellg(); + + unsigned char parse_flags = 0; + if (flags & XPATH_PARSE_NO_MIGRATE) + parse_flags |= AMOUNT_PARSE_NO_MIGRATE; + if (flags & XPATH_PARSE_NO_REDUCE) + parse_flags |= AMOUNT_PARSE_NO_REDUCE; + + temp.parse(in, parse_flags); + + kind = VALUE; + value = temp; + } + catch (amount_exception& err) { + // If the amount had no commodity, it must be an unambiguous + // variable reference + + // jww (2007-04-19): There must be a more efficient way to do this! + if (std::strcmp(err.what(), "No quantity specified for amount") == 0) { + in.clear(); + in.seekg(pos, std::ios::beg); + + c = in.peek(); + assert(! (std::isdigit(c) || c == '.')); + parse_ident(in); + } else { + throw; + } + } + } + break; + } +} + +void xpath_t::token_t::rewind(std::istream& in) +{ + for (unsigned int i = 0; i < length; i++) + in.unget(); +} + + +void xpath_t::token_t::unexpected() +{ + switch (kind) { + case TOK_EOF: + throw_(parse_exception, "Unexpected end of expression"); + case IDENT: + throw_(parse_exception, "Unexpected symbol '" << value << "'"); + case VALUE: + throw_(parse_exception, "Unexpected value '" << value << "'"); + default: + throw_(parse_exception, "Unexpected operator '" << symbol << "'"); + } +} + +void xpath_t::token_t::unexpected(char c, char wanted) +{ + if ((unsigned char) c == 0xff) { + if (wanted) + throw_(parse_exception, "Missing '" << wanted << "'"); + else + throw_(parse_exception, "Unexpected end"); + } else { + if (wanted) + throw_(parse_exception, "Invalid char '" << c << + "' (wanted '" << wanted << "')"); + else + throw_(parse_exception, "Invalid char '" << c << "'"); + } +} + +xpath_t::op_t * xpath_t::wrap_value(const value_t& val) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::VALUE); + temp->valuep = new value_t(val); + return temp; +} + +xpath_t::op_t * xpath_t::wrap_sequence(value_t::sequence_t * val) +{ + if (val->size() == 0) + return wrap_value(false); + else if (val->size() == 1) + return wrap_value(val->front()); + else + return wrap_value(val); +} + +xpath_t::op_t * xpath_t::wrap_functor(functor_t * fobj) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::FUNCTOR); + temp->functor = fobj; + return temp; +} + +#if 0 +xpath_t::op_t * xpath_t::wrap_mask(const string& pattern) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::MASK); + temp->mask = new mask_t(pattern); + return temp; +} +#endif + +void xpath_t::scope_t::define(const string& name, op_t * def) +{ + DEBUG_("ledger.xpath.syms", "Defining '" << name << "' = " << def); + + std::pair result + = symbols.insert(symbol_pair(name, def)); + if (! result.second) { + symbol_map::iterator i = symbols.find(name); + assert(i != symbols.end()); + (*i).second->release(); + symbols.erase(i); + + std::pair result2 + = symbols.insert(symbol_pair(name, def)); + if (! result2.second) + throw_(compile_exception, + "Redefinition of '" << name << "' in same scope"); + } + def->acquire(); +} + +xpath_t::op_t * +xpath_t::scope_t::lookup(const 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; +} + +void xpath_t::scope_t::define(const string& name, functor_t * def) { + define(name, wrap_functor(def)); +} + +bool xpath_t::function_scope_t::resolve(const string& name, + value_t& result, + scope_t * locals) +{ + switch (name[0]) { + case 'l': + if (name == "last") { + if (sequence) + result = (long)sequence->size(); + else + result = 1L; + return true; + } + break; + + case 'p': + if (name == "position") { + result = (long)index + 1; + return true; + } + break; + + case 't': + if (name == "text") { + if (value->type == value_t::XML_NODE) + result.set_string(value->to_xml_node()->text()); + else + throw_(calc_exception, "Attempt to call text() on a non-node value"); + return true; + } + break; + } + return scope_t::resolve(name, result, locals); +} + +xpath_t::op_t::~op_t() +{ + TRACE_DTOR(xpath_t::op_t); + + DEBUG_("ledger.xpath.memory", "Destroying " << this); + assert(refc == 0); + + switch (kind) { + case VALUE: + assert(! left); + assert(valuep); + delete valuep; + break; + + case NODE_NAME: + case FUNC_NAME: + case ATTR_NAME: + case VAR_NAME: + assert(! left); + assert(name); + delete name; + break; + + case ARG_INDEX: + break; + + case FUNCTOR: + assert(! left); + assert(functor); + delete functor; + break; + +#if 0 + case MASK: + assert(! left); + assert(mask); + delete mask; + break; +#endif + + default: + assert(kind < LAST); + if (left) + left->release(); + if (kind > TERMINALS && right) + right->release(); + break; + } +} + +void xpath_t::op_t::get_value(value_t& result) const +{ + switch (kind) { + case VALUE: + result = *valuep; + break; + case ARG_INDEX: + result = (long)arg_index; + break; + default: + throw_(calc_exception, + "Cannot determine value of expression symbol '" << *this << "'"); + } +} + +xpath_t::op_t * +xpath_t::parse_value_term(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::VALUE: + node.reset(new op_t(op_t::VALUE)); + node->valuep = new value_t(tok.value); + break; + + case token_t::IDENT: { +#if 0 +#ifdef USE_BOOST_PYTHON + if (tok.value->to_string() == "lambda") // special + try { + char c, buf[4096]; + + std::strcpy(buf, "lambda "); + READ_INTO(in, &buf[7], 4000, c, true); + + op_t * eval = new op_t(op_t::O_EVAL); + op_t * lambda = new op_t(op_t::FUNCTOR); + lambda->functor = new python_functor_t(python_eval(buf)); + eval->set_left(lambda); + op_t * sym = new op_t(op_t::SYMBOL); + sym->name = new string("__ptr"); + eval->set_right(sym); + + node.reset(eval); + + goto done; + } + catch(const boost::python::error_already_set&) { + throw_(parse_exception, "Error parsing lambda expression"); + } +#endif /* USE_BOOST_PYTHON */ +#endif + + string ident = tok.value.to_string(); + int id = -1; + if (std::isdigit(ident[0])) { + node.reset(new op_t(op_t::ARG_INDEX)); + node->arg_index = std::atol(ident.c_str()); + } + else if ((id = document_t::lookup_builtin_id(ident)) != -1) { + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = id; + } + else { + node.reset(new op_t(op_t::NODE_NAME)); + node->name = new string(ident); + } + + // An identifier followed by ( represents a function call + tok = next_token(in, tflags); + if (tok.kind == token_t::LPAREN) { + node->kind = op_t::FUNC_NAME; + + std::auto_ptr call_node; + call_node.reset(new op_t(op_t::O_EVAL)); + call_node->set_left(node.release()); + call_node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(); // jww (2006-09-09): wanted ) + + node.reset(call_node.release()); + } else { + push_token(tok); + } + break; + } + + case token_t::AT_SYM: + tok = next_token(in, tflags); + if (tok.kind != token_t::IDENT) + throw_(parse_exception, "@ symbol must be followed by attribute name"); + + node.reset(new op_t(op_t::ATTR_NAME)); + node->name = new string(tok.value.to_string()); + break; + +#if 0 + case token_t::DOLLAR: + tok = next_token(in, tflags); + if (tok.kind != token_t::IDENT) + throw parse_error("$ symbol must be followed by variable name"); + + node.reset(new op_t(op_t::VAR_NAME)); + node->name = new string(tok.value.to_string()); + break; +#endif + + case token_t::DOT: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::CURRENT; + break; + case token_t::DOTDOT: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::PARENT; + break; + case token_t::SLASH: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::ROOT; + push_token(); + break; + case token_t::STAR: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::ALL; + break; + + case token_t::LPAREN: + node.reset(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); + if (! node.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(); // jww (2006-09-09): wanted ) + break; + +#if 0 + case token_t::REGEXP: + node.reset(wrap_mask(tok.value.to_string())); + break; +#endif + + default: + push_token(tok); + break; + } + +#if 0 +#ifdef USE_BOOST_PYTHON + done: +#endif +#endif + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_predicate_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_value_term(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + while (tok.kind == token_t::LBRACKET) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_PRED)); + node->set_left(prev.release()); + node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); + if (! node->right) + throw_(parse_exception, "[ operator not followed by valid expression"); + + tok = next_token(in, tflags); + if (tok.kind != token_t::RBRACKET) + tok.unexpected(); // jww (2006-09-09): wanted ] + + tok = next_token(in, tflags); + } + + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_path_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_predicate_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + while (tok.kind == token_t::SLASH) { + std::auto_ptr prev(node.release()); + + tok = next_token(in, tflags); + node.reset(new op_t(tok.kind == token_t::SLASH ? + op_t::O_RFIND : op_t::O_FIND)); + if (tok.kind != token_t::SLASH) + push_token(tok); + + node->set_left(prev.release()); + node->set_right(parse_predicate_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, "/ operator not followed by a valid term"); + + tok = next_token(in, tflags); + } + + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_unary_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node; + + token_t& tok = next_token(in, tflags); + + switch (tok.kind) { + case token_t::EXCLAM: { + std::auto_ptr texpr(parse_path_expr(in, tflags)); + if (! texpr.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + // A very quick optimization + if (texpr->kind == op_t::VALUE) { + *texpr->valuep = ! *texpr->valuep; + node.reset(texpr.release()); + } else { + node.reset(new op_t(op_t::O_NOT)); + node->set_left(texpr.release()); + } + break; + } + + case token_t::MINUS: { + std::auto_ptr texpr(parse_path_expr(in, tflags)); + if (! texpr.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + // A very quick optimization + if (texpr->kind == op_t::VALUE) { + texpr->valuep->in_place_negate(); + node.reset(texpr.release()); + } else { + node.reset(new op_t(op_t::O_NEG)); + node->set_left(texpr.release()); + } + break; + } + +#if 0 + case token_t::PERCENT: { + std::auto_ptr texpr(parse_path_expr(in, tflags)); + if (! texpr.get()) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + // A very quick optimization + if (texpr->kind == op_t::VALUE) { + static value_t perc("100.0%"); + *texpr->valuep = perc * *texpr->valuep; + node.reset(texpr.release()); + } else { + node.reset(new op_t(op_t::O_PERC)); + node->set_left(texpr.release()); + } + break; + } +#endif + + default: + push_token(tok); + node.reset(parse_path_expr(in, tflags)); + break; + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_union_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_unary_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::PIPE || tok.kind == token_t::KW_UNION) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_UNION)); + node->set_left(prev.release()); + node->set_right(parse_union_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_mul_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_union_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(tok.kind == token_t::STAR ? + op_t::O_MUL : op_t::O_DIV)); + node->set_left(prev.release()); + node->set_right(parse_mul_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_add_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_mul_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::PLUS || + tok.kind == token_t::MINUS) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(tok.kind == token_t::PLUS ? + op_t::O_ADD : op_t::O_SUB)); + node->set_left(prev.release()); + node->set_right(parse_add_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + + tok = next_token(in, tflags); + } + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_logic_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_add_expr(in, tflags)); + + if (node.get()) { + op_t::kind_t kind = op_t::LAST; + + unsigned short _flags = tflags; + + token_t& tok = next_token(in, tflags); + switch (tok.kind) { + case token_t::ASSIGN: + kind = op_t::O_DEFINE; + break; + case token_t::EQUAL: + kind = op_t::O_EQ; + break; + case token_t::NEQUAL: + kind = op_t::O_NEQ; + break; +#if 0 + case token_t::MATCH: + kind = op_t::O_MATCH; + _flags |= XPATH_PARSE_REGEXP; + break; + case token_t::NMATCH: + kind = op_t::O_NMATCH; + _flags |= XPATH_PARSE_REGEXP; + break; +#endif + 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) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(kind)); + node->set_left(prev.release()); + if (kind == op_t::O_DEFINE) + node->set_right(parse_querycolon_expr(in, tflags)); + else + node->set_right(parse_add_expr(in, _flags)); + + if (! node->right) { + if (tok.kind == token_t::PLUS) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + else + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } + } + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_and_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_logic_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::KW_AND) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_AND)); + node->set_left(prev.release()); + node->set_right(parse_and_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_or_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_and_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::KW_OR) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_OR)); + node->set_left(prev.release()); + node->set_right(parse_or_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_querycolon_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_or_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::QUESTION) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_QUES)); + node->set_left(prev.release()); + node->set_right(new op_t(op_t::O_COLON)); + node->right->set_left(parse_querycolon_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + if (tok.kind != token_t::COLON) + tok.unexpected(); // jww (2006-09-09): wanted : + node->right->set_right(parse_querycolon_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_value_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_querycolon_expr(in, tflags)); + + if (node.get()) { + token_t& tok = next_token(in, tflags); + if (tok.kind == token_t::COMMA) { + std::auto_ptr prev(node.release()); + node.reset(new op_t(op_t::O_COMMA)); + node->set_left(prev.release()); + node->set_right(parse_value_expr(in, tflags)); + if (! node->right) + throw_(parse_exception, + tok.symbol << " operator not followed by argument"); + tok = next_token(in, tflags); + } + + if (tok.kind != token_t::TOK_EOF) { + if (tflags & XPATH_PARSE_PARTIAL) + push_token(tok); + else + tok.unexpected(); + } + } + else if (! (tflags & XPATH_PARSE_PARTIAL)) { + throw_(parse_exception, "Failed to parse value expression"); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_expr(std::istream& in, unsigned short tflags) const +{ + std::auto_ptr node(parse_value_expr(in, tflags)); + + if (use_lookahead) { + use_lookahead = false; +#ifdef THREADSAFE + lookahead.rewind(in); +#else + lookahead->rewind(in); +#endif + } +#ifdef THREADSAFE + lookahead.clear(); +#else + lookahead->clear(); +#endif + + return node.release(); +} + +xpath_t::op_t * +xpath_t::op_t::new_node(kind_t kind, op_t * left, op_t * right) +{ + std::auto_ptr node(new op_t(kind)); + if (left) + node->set_left(left); + if (right) + node->set_right(right); + return node.release(); +} + +xpath_t::op_t * +xpath_t::op_t::copy(op_t * tleft, op_t * tright) const +{ + std::auto_ptr node(new op_t(kind)); + if (tleft) + node->set_left(tleft); + if (tright) + node->set_right(tright); + return node.release(); +} + +void xpath_t::op_t::find_values(value_t * context, scope_t * scope, + value_t::sequence_t& result_seq, + bool recursive) +{ + xpath_t expr(compile(context, scope, true)); + + if (expr->kind == VALUE) + append_value(*expr->valuep, result_seq); + + if (recursive) { + if (context->type == value_t::XML_NODE) { + node_t * ptr = context->to_xml_node(); + if (ptr->flags & XML_NODE_IS_PARENT) { + parent_node_t * parent = static_cast(ptr); + for (node_t * node = parent->children(); + node; + node = node->next) { + value_t temp(node); + find_values(&temp, scope, result_seq, recursive); + } + } + } else { + throw_(calc_exception, "Recursive path selection on a non-node value"); + } + } +} + +bool xpath_t::op_t::test_value(value_t * context, scope_t * scope, + int index) +{ + xpath_t expr(compile(context, scope, true)); + + if (expr->kind != VALUE) + throw_(calc_exception, "Predicate expression does not yield a constant value"); + + switch (expr->valuep->type) { + case value_t::INTEGER: + case value_t::AMOUNT: + return *expr->valuep == (long)index + 1; + + default: + return expr->valuep->to_boolean(); + } +} + +xpath_t::op_t * xpath_t::op_t::defer_sequence(value_t::sequence_t& result_seq) +{ + // If not all of the elements were constants, transform the result + // into an expression sequence using O_COMMA. + + assert(! result_seq.empty()); + + if (result_seq.size() == 1) + return wrap_value(result_seq.front())->acquire(); + + value_t::sequence_t::iterator i = result_seq.begin(); + + std::auto_ptr lit_seq(new op_t(O_COMMA)); + + lit_seq->set_left(wrap_value(*i++)); + op_t ** opp = &lit_seq->right; + + for (; i != result_seq.end(); i++) { + if (*opp) { + op_t * val = *opp; + *opp = new op_t(O_COMMA); + (*opp)->set_left(val); + opp = &(*opp)->right; + } + + if ((*i).type != value_t::POINTER) + *opp = wrap_value(*i)->acquire(); + else + *opp = static_cast((*i).to_pointer()); + } + + return lit_seq.release(); +} + +void xpath_t::op_t::append_value(value_t& val, + value_t::sequence_t& result_seq) +{ + if (val.type == value_t::SEQUENCE) { + value_t::sequence_t * subseq = val.to_sequence(); + for (value_t::sequence_t::iterator i = subseq->begin(); + i != subseq->end(); + i++) + result_seq.push_back(*i); + } else { + result_seq.push_back(val); + } +} + +xpath_t::op_t * xpath_t::op_t::compile(value_t * context, scope_t * scope, + bool resolve) +{ +#if 0 + try { +#endif + switch (kind) { + case VALUE: + return acquire(); + + case NODE_ID: + switch (name_id) { + case document_t::CURRENT: + return wrap_value(context)->acquire(); + + case document_t::PARENT: + if (context->type != value_t::XML_NODE) + throw_(compile_exception, "Referencing parent node from a non-node value"); + else if (context->to_xml_node()->parent) + return wrap_value(context->to_xml_node()->parent)->acquire(); + else + throw_(compile_exception, "Referencing parent node from the root node"); + + case document_t::ROOT: + if (context->type != value_t::XML_NODE) + throw_(compile_exception, "Referencing root node from a non-node value"); + else + return wrap_value(context->to_xml_node()->document->top)->acquire(); + + case document_t::ALL: { + if (context->type != value_t::XML_NODE) + throw_(compile_exception, "Referencing child nodes from a non-node value"); + + node_t * ptr = context->to_xml_node(); + if (! (ptr->flags & XML_NODE_IS_PARENT)) + throw_(compile_exception, "Request for child nodes of a leaf node"); + + parent_node_t * parent = static_cast(ptr); + + value_t::sequence_t * nodes = new value_t::sequence_t; + for (node_t * node = parent->children(); node; node = node->next) + nodes->push_back(node); + + return wrap_value(nodes)->acquire(); + } + + default: + break; // pass down to the NODE_NAME case + } + // fall through... + + case NODE_NAME: + if (context->type == value_t::XML_NODE) { + node_t * ptr = context->to_xml_node(); + if (resolve) { + // First, look up the symbol as a node name within the current + // context. If any exist, then return the set of names. + + std::auto_ptr nodes(new value_t::sequence_t); + + if (ptr->flags & XML_NODE_IS_PARENT) { + parent_node_t * parent = static_cast(ptr); + for (node_t * node = parent->children(); + node; + node = node->next) { + if ((kind == NODE_NAME && + std::strcmp(name->c_str(), node->name()) == 0) || + (kind == NODE_ID && name_id == node->name_id)) + nodes->push_back(node); + } + } + return wrap_value(nodes.release())->acquire(); + } else { + assert(ptr); + int id = ptr->document->lookup_name_id(*name); + if (id != -1) { + op_t * node = new_node(NODE_ID); + node->name_id = id; + return node->acquire(); + } + } + } + return acquire(); + + case ATTR_NAME: { + // jww (2006-09-29): Attrs should map strings to values, not strings + const char * value = context->to_xml_node()->get_attr(name->c_str()); + return wrap_value(value)->acquire(); + } + + case VAR_NAME: + case FUNC_NAME: + if (scope) { + if (resolve) { + value_t temp; + if (scope->resolve(*name, temp)) + return wrap_value(temp)->acquire(); + } + if (op_t * def = scope->lookup(*name)) + return def->compile(context, scope, resolve); + } + return acquire(); + + case ARG_INDEX: + if (scope && scope->kind == scope_t::ARGUMENT) { + assert(scope->args.type == value_t::SEQUENCE); + if (arg_index < scope->args.to_sequence()->size()) + return wrap_value((*scope->args.to_sequence())[arg_index])->acquire(); + else + throw_(compile_exception, "Reference to non-existing argument"); + } else { + return acquire(); + } + + case FUNCTOR: + if (resolve) { + value_t temp; + (*functor)(temp, scope); + return wrap_value(temp)->acquire(); + } else { + return acquire(); + } + break; + +#if 0 + case MASK: + return acquire(); +#endif + + case O_NOT: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + if (left == expr) { + if (expr->valuep->strip_annotations()) + return wrap_value(false)->acquire(); + else + return wrap_value(true)->acquire(); + } else { + if (expr->valuep->strip_annotations()) + *expr->valuep = false; + else + *expr->valuep = true; + + return expr->acquire(); + } + } + + case O_NEG: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + if (left == expr) { + return wrap_value(expr->valuep->negate())->acquire(); + } else { + expr->valuep->in_place_negate(); + return expr->acquire(); + } + } + + case O_UNION: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + std::auto_ptr result_seq(new value_t::sequence_t); + + append_value(*lexpr->valuep, *result_seq); + append_value(*rexpr->valuep, *result_seq); + + if (result_seq->size() == 1) + return wrap_value(result_seq->front())->acquire(); + else + return wrap_sequence(result_seq.release())->acquire(); + break; + } + + case O_ADD: + case O_SUB: + case O_MUL: + case O_DIV: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (left == lexpr) { + value_t temp(*lexpr->valuep); + switch (kind) { + case O_ADD: temp += *rexpr->valuep; break; + case O_SUB: temp -= *rexpr->valuep; break; + case O_MUL: temp *= *rexpr->valuep; break; + case O_DIV: temp /= *rexpr->valuep; break; + default: assert(0); break; + } + return wrap_value(temp)->acquire(); + } else { + switch (kind) { + case O_ADD: *lexpr->valuep += *rexpr->valuep; break; + case O_SUB: *lexpr->valuep -= *rexpr->valuep; break; + case O_MUL: *lexpr->valuep *= *rexpr->valuep; break; + case O_DIV: *lexpr->valuep /= *rexpr->valuep; break; + default: assert(0); break; + } + return lexpr->acquire(); + } + } + + case O_NEQ: + case O_EQ: + case O_LT: + case O_LTE: + case O_GT: + case O_GTE: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (left == lexpr) { + switch (kind) { + case O_NEQ: + return wrap_value(*lexpr->valuep != *rexpr->valuep)->acquire(); + break; + case O_EQ: + return wrap_value(*lexpr->valuep == *rexpr->valuep)->acquire(); + break; + case O_LT: + return wrap_value(*lexpr->valuep < *rexpr->valuep)->acquire(); + break; + case O_LTE: + return wrap_value(*lexpr->valuep <= *rexpr->valuep)->acquire(); + break; + case O_GT: + return wrap_value(*lexpr->valuep > *rexpr->valuep)->acquire(); + break; + case O_GTE: + return wrap_value(*lexpr->valuep >= *rexpr->valuep)->acquire(); + break; + default: assert(0); break; + } + } else { + switch (kind) { + case O_NEQ: *lexpr->valuep = *lexpr->valuep != *rexpr->valuep; break; + case O_EQ: *lexpr->valuep = *lexpr->valuep == *rexpr->valuep; break; + case O_LT: *lexpr->valuep = *lexpr->valuep < *rexpr->valuep; break; + case O_LTE: *lexpr->valuep = *lexpr->valuep <= *rexpr->valuep; break; + case O_GT: *lexpr->valuep = *lexpr->valuep > *rexpr->valuep; break; + case O_GTE: *lexpr->valuep = *lexpr->valuep >= *rexpr->valuep; break; + default: assert(0); break; + } + return lexpr->acquire(); + } + } + + case O_AND: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (lexpr->constant() && ! lexpr->valuep->strip_annotations()) { + *lexpr->valuep = false; + return lexpr->acquire(); + } + + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (! rexpr->valuep->strip_annotations()) { + if (left == lexpr) { + return wrap_value(false)->acquire(); + } else { + *lexpr->valuep = false; + return lexpr->acquire(); + } + } else { + return rexpr->acquire(); + } + } + + case O_OR: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (lexpr->constant() && lexpr->valuep->strip_annotations()) + return lexpr->acquire(); + + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (rexpr->valuep->strip_annotations()) { + return rexpr->acquire(); + } else { + if (left == lexpr) { + return wrap_value(false)->acquire(); + } else { + *lexpr->valuep = false; + return lexpr->acquire(); + } + } + } + + case O_QUES: { + assert(left); + assert(right); + assert(right->kind == O_COLON); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (! lexpr->constant()) { + xpath_t rexpr(right->compile(context, scope, resolve)); + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (lexpr->valuep->strip_annotations()) + return right->left->compile(context, scope, resolve); + else + return right->right->compile(context, scope, resolve); + } + + case O_COLON: { + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + case O_COMMA: { + assert(left); + assert(right); + // jww (2006-09-29): This should act just like union + xpath_t lexpr(left->compile(context, scope, resolve)); // for side-effects + return right->compile(context, scope, resolve); + } + +#if 0 + case O_MATCH: + case O_NMATCH: { + assert(left); + assert(right); + xpath_t rexpr(right->compile(context, scope, resolve)); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (! lexpr->constant() || rexpr->kind != MASK) { + if (left == lexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (lexpr->valuep->type != value_t::STRING) + throw_(compile_exception, "Left operand of mask operator is not a string"); + + assert(rexpr->mask); + + bool result = rexpr->mask->match(lexpr->valuep->to_string()); + if (kind == O_NMATCH) + result = ! result; + + if (left == lexpr) { + return wrap_value(result)->acquire(); + } else { + *lexpr->valuep = result; + return lexpr->acquire(); + } + } +#endif + + case O_DEFINE: + assert(left); + assert(right); + if (left->kind == VAR_NAME || left->kind == FUNC_NAME) { + xpath_t rexpr(right->compile(context, scope, resolve)); + if (scope) + scope->define(*left->name, rexpr); + return rexpr->acquire(); + } else { + assert(left->kind == O_EVAL); + assert(left->left->kind == FUNC_NAME); + + std::auto_ptr arg_scope(new scope_t(scope)); + + int index = 0; + op_t * args = left->right; + while (args) { + op_t * arg = args; + if (args->kind == O_COMMA) { + arg = args->left; + args = args->right; + } else { + args = NULL; + } + + // Define the parameter so that on lookup the parser will find + // an ARG_INDEX value. + std::auto_ptr ref(new op_t(ARG_INDEX)); + ref->arg_index = index++; + + assert(arg->kind == NODE_NAME); + arg_scope->define(*arg->name, ref.release()); + } + + // jww (2006-09-16): If I compile the definition of a function, + // I eliminate the possibility of future lookups + //xpath_t rexpr(right->compile(arg_scope.get(), resolve)); + + if (scope) + scope->define(*left->left->name, right); + + return right->acquire(); + } + + case O_EVAL: { + assert(left); + + std::auto_ptr call_args(new scope_t(scope)); + call_args->kind = scope_t::ARGUMENT; + + std::auto_ptr call_seq; + + op_t * args = right; + while (args) { + op_t * arg = args; + if (args->kind == O_COMMA) { + arg = args->left; + args = args->right; + } else { + args = NULL; + } + + if (! call_seq.get()) + call_seq.reset(new value_t::sequence_t); + + // jww (2006-09-15): Need to return a reference to these, if + // there are undetermined arguments! + call_seq->push_back(arg->compile(context, scope, resolve)->value()); + } + + if (call_seq.get()) + call_args->args = call_seq.release(); + + if (left->kind == FUNC_NAME) { + if (resolve) { + value_t temp; + if (scope && scope->resolve(*left->name, temp, call_args.get())) + return wrap_value(temp)->acquire(); + } + + // Don't compile to the left, otherwise the function name may + // get resolved before we have a chance to call it + xpath_t func(left->compile(context, scope, false)); + if (func->kind == FUNCTOR) { + value_t temp; + (*func->functor)(temp, call_args.get()); + return wrap_value(temp)->acquire(); + } + else if (! resolve) { + return func->compile(context, call_args.get(), resolve); + } + else { + throw_(calc_exception, "Unknown function name '" << *left->name << "'"); + } + } + else if (left->kind == FUNCTOR) { + value_t temp; + (*left->functor)(temp, call_args.get()); + return wrap_value(temp)->acquire(); + } + else { + assert(0); + } + break; + } + + case O_FIND: + case O_RFIND: + case O_PRED: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(resolve ? right->acquire() : + right->compile(context, scope, false)); + + if (! lexpr->constant() || ! resolve) { + if (left == lexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + std::auto_ptr result_seq(new value_t::sequence_t); + + // jww (2006-09-24): What about when nothing is found? + switch (lexpr->valuep->type) { + case value_t::XML_NODE: { + function_scope_t xpath_fscope(NULL, lexpr->valuep, 0, scope); + if (kind == O_PRED) { + if (rexpr->test_value(lexpr->valuep, &xpath_fscope)) + result_seq->push_back(*lexpr->valuep); + } else { + rexpr->find_values(lexpr->valuep, &xpath_fscope, *result_seq.get(), + kind == O_RFIND); + } + break; + } + + case value_t::SEQUENCE: { + value_t::sequence_t * seq = lexpr->valuep->to_sequence(); + + int index = 0; + for (value_t::sequence_t::iterator i = seq->begin(); + i != seq->end(); + i++, index++) { + assert((*i).type != value_t::SEQUENCE); + if ((*i).type != value_t::XML_NODE) + throw_(compile_exception, "Attempting to apply path selection " + "to non-node(s)"); + + function_scope_t xpath_fscope(seq, &(*i), index, scope); + if (kind == O_PRED) { + if (rexpr->test_value(&(*i), &xpath_fscope, index)) + result_seq->push_back(*i); + } else { + rexpr->find_values(&(*i), &xpath_fscope, *result_seq.get(), + kind == O_RFIND); + } + } + break; + } + + default: + throw_(compile_exception, "Attempting to apply path selection " + "to non-node(s)"); + } + + if (result_seq->size() == 1) + return wrap_value(result_seq->front())->acquire(); + else + return wrap_sequence(result_seq.release())->acquire(); + } + +#if 0 + case O_PERC: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + static value_t perc("100.0%"); + *expr->valuep = perc * *expr->valuep; + return expr->acquire(); + } +#endif + + case LAST: + default: + assert(0); + break; + } +#if 0 + } + catch (error * err) { +#if 0 + // jww (2006-09-09): I need a reference to the parent xpath_t + if (err->context.empty() || + ! dynamic_cast(err->context.back())) + err->context.push_back(new context(this)); +#endif + throw err; + } +#endif + + assert(0); + return NULL; +} + +void xpath_t::calc(value_t& result, node_t * node, scope_t * scope) const +{ +#if 0 + try { +#endif + if (node) { + value_t context_node(node); + xpath_t final(ptr->compile(&context_node, scope, true)); + // jww (2006-09-09): Give a better error here if this is not + // actually a value + final->get_value(result); + } else { + std::auto_ptr fake_node(new terminal_node_t(NULL)); + value_t context_node(fake_node.get()); + xpath_t final(ptr->compile(&context_node, scope, true)); + final->get_value(result); + } +#if 0 + } + catch (error * err) { + if (err->context.empty() || + ! dynamic_cast(err->context.back())) + err->context.push_back + (new context(*this, ptr, "While calculating value expression:")); +#if 0 + error_context * last = err->context.back(); + if (context * ctxt = dynamic_cast(last)) { + ctxt->xpath = *this; + ctxt->desc = "While calculating value expression:"; + } +#endif + throw err; + } +#endif +} + +#if 0 +xpath_t::context::context(const xpath_t& _xpath, + const op_t * _err_node, + const string& desc) throw() + : error_context(desc), xpath(_xpath), err_node(_err_node) +{ + _err_node->acquire(); +} + +xpath_t::context::~context() throw() +{ + if (err_node) err_node->release(); +} + +void xpath_t::context::describe(std::ostream& out) const throw() +{ + if (! xpath) { + out << "xpath_t::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 = false; + if (xpath) + xpath.write(out, true, err_node, &begin, &end); + out << std::endl; + if (found) { + out << " "; + for (unsigned int i = 0; i < end - start; i++) { + if (i >= begin - start) + out << "^"; + else + out << " "; + } + out << std::endl; + } +} +#endif + +bool xpath_t::op_t::write(std::ostream& out, + const bool relaxed, + const op_t * op_to_find, + unsigned long * start_pos, + unsigned long * end_pos) const +{ + int arg_index = 0; + bool found = false; + + if (start_pos && this == op_to_find) { + *start_pos = (long)out.tellp() - 1; + found = true; + } + + string symbol; + + switch (kind) { + case VALUE: + switch (valuep->type) { + case value_t::BOOLEAN: + if (*(valuep)) + out << "1"; + else + out << "0"; + break; + case value_t::INTEGER: + case value_t::AMOUNT: + if (! relaxed) + out << '{'; + out << *(valuep); + if (! relaxed) + out << '}'; + break; + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + assert(0); + break; + case value_t::DATETIME: + out << '[' << *valuep << ']'; + break; + case value_t::STRING: + out << '"' << *valuep << '"'; + break; + + case value_t::XML_NODE: + out << '<' << valuep << '>'; + break; + case value_t::POINTER: + out << '&' << valuep; + break; + case value_t::SEQUENCE: + out << '~' << valuep << '~'; + break; + } + break; + + case NODE_ID: +#ifdef THREADSAFE + out << '%' << name_id; +#else + out << node_t::document->lookup_name(name_id); +#endif + break; + + case NODE_NAME: + case FUNC_NAME: + out << *name; + break; + + case ATTR_NAME: + out << '@' << *name; + break; + + case VAR_NAME: + out << '$' << *name; + break; + + case FUNCTOR: + out << functor->name(); + break; + +#if 0 + case MASK: + out << '/' << mask->pattern << '/'; + break; +#endif + + case ARG_INDEX: + out << '@' << arg_index; + break; + + case O_NOT: + out << "!"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_NEG: + out << "-"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_UNION: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " | "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_ADD: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " + "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_SUB: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " - "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_MUL: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " * "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_DIV: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " / "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_NEQ: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " != "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_EQ: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " == "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_LT: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " < "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_LTE: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " <= "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_GT: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " > "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_GTE: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " >= "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_AND: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " & "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_OR: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " | "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_QUES: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " ? "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_COLON: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " : "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_COMMA: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ", "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + +#if 0 + case O_MATCH: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " =~ "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_NMATCH: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " !~ "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; +#endif + + case O_DEFINE: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << '='; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_EVAL: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "("; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_FIND: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "/"; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_RFIND: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "//"; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_PRED: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "["; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "]"; + break; + +#if 0 + case O_PERC: + out << "%"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; +#endif + + case LAST: + default: + assert(0); + break; + } + + if (! symbol.empty()) { + if (commodity_t::find(symbol)) + out << '@'; + out << symbol; + } + + if (end_pos && this == op_to_find) + *end_pos = (long)out.tellp() - 1; + + return found; +} + +void xpath_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 - " << *valuep; + break; + + case NODE_NAME: + out << "NODE_NAME - " << *name; + break; + + case NODE_ID: +#ifdef THREADSAFE + out << "NODE_ID - " << name_id; +#else + out << "NODE_ID - " << node_t::document->lookup_name(name_id); +#endif + break; + + case ATTR_NAME: + out << "ATTR_NAME - " << *name; + break; + + case FUNC_NAME: + out << "FUNC_NAME - " << *name; + break; + + case VAR_NAME: + out << "VAR_NAME - " << *name; + break; + + case ARG_INDEX: + out << "ARG_INDEX - " << arg_index; + break; + + case FUNCTOR: + out << "FUNCTOR - " << functor->name(); + break; +#if 0 + case MASK: + out << "MASK - " << mask->pattern; + break; +#endif + + case O_NOT: out << "O_NOT"; break; + case O_NEG: out << "O_NEG"; break; + + case O_UNION: out << "O_UNION"; 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_QUES: out << "O_QUES"; break; + case O_COLON: out << "O_COLON"; break; + + case O_COMMA: out << "O_COMMA"; break; + +#if 0 + case O_MATCH: out << "O_MATCH"; break; + case O_NMATCH: out << "O_NMATCH"; break; +#endif + + case O_DEFINE: out << "O_DEFINE"; break; + case O_EVAL: out << "O_EVAL"; break; + + case O_FIND: out << "O_FIND"; break; + case O_RFIND: out << "O_RFIND"; break; + case O_PRED: out << "O_PRED"; break; + +#if 0 + case O_PERC: out << "O_PERC"; break; +#endif + + case LAST: + default: + assert(0); + 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); + } + } else { + assert(! left); + } +} + +} // namespace xml +} // namespace ledger diff --git a/src/xpath.h b/src/xpath.h new file mode 100644 index 00000000..13966ffc --- /dev/null +++ b/src/xpath.h @@ -0,0 +1,783 @@ +#ifndef _XPATH_H +#define _XPATH_H + +#include "xml.h" + +namespace ledger { +namespace xml { + +class xpath_t +{ +public: + struct op_t; + + static void initialize(); + static void shutdown(); + + DECLARE_EXCEPTION(parse_exception); + DECLARE_EXCEPTION(compile_exception); + DECLARE_EXCEPTION(calc_exception); + +#if 0 + class context : public error_context { + public: + const xpath_t& xpath; + const op_t * err_node; + + context(const xpath_t& _xpath, + const op_t * _err_node, + const string& desc = "") throw(); + virtual ~context() throw(); + + virtual void describe(std::ostream& out) const throw(); + }; +#endif + +public: + class scope_t; + + class functor_t { + protected: + string fname; + public: + bool wants_args; + + functor_t(const string& _fname, bool _wants_args = false) + : fname(_fname), wants_args(_wants_args) {} + virtual ~functor_t() {} + + virtual void operator()(value_t& result, scope_t * locals) = 0; + virtual string name() const { return fname; } + }; + + template + class member_functor_t : public functor_t { + public: + T * ptr; + U T::*dptr; + + member_functor_t(const string& _name, T * _ptr, U T::*_dptr) + : functor_t(_name, false), ptr(_ptr), dptr(_dptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(dptr); + result = ptr->*dptr; + } + }; + + template + class member_functor_t : public functor_t { + public: + T * ptr; + string T::*dptr; + + member_functor_t(const string& _name, T * _ptr, string T::*_dptr) + : functor_t(_name, false), ptr(_ptr), dptr(_dptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(dptr); + result.set_string(ptr->*dptr); + } + }; + + template + class memfun_functor_t : public functor_t { + public: + T * ptr; + void (T::*mptr)(value_t& result); + + memfun_functor_t(const string& _name, T * _ptr, + void (T::*_mptr)(value_t& result)) + : functor_t(_name, false), ptr(_ptr), mptr(_mptr) {} + + virtual void operator()(value_t& result, + scope_t * locals = NULL) { + assert(ptr); + assert(mptr); + assert(locals || locals == NULL); + (ptr->*mptr)(result); + } + }; + + template + class memfun_args_functor_t : public functor_t { + public: + T * ptr; + void (T::*mptr)(value_t& result, scope_t * locals); + + memfun_args_functor_t(const string& _name, T * _ptr, + void (T::*_mptr)(value_t& result, scope_t * locals)) + : functor_t(_name, true), ptr(_ptr), mptr(_mptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(mptr); + (ptr->*mptr)(result, locals); + } + }; + + static op_t * wrap_value(const value_t& val); + static op_t * wrap_sequence(value_t::sequence_t * val); + static op_t * wrap_functor(functor_t * fobj); +#if 0 + static op_t * wrap_mask(const string& pattern); +#endif + + template + static op_t * + make_functor(const string& name, T * ptr, U T::*mptr) { + return wrap_functor(new member_functor_t(name, ptr, mptr)); + } + + template + static op_t * + make_functor(const string& fname, T * ptr, + void (T::*mptr)(value_t& result)) { + return wrap_functor(new memfun_functor_t(fname, ptr, mptr)); + } + + template + static op_t * + make_functor(const string& fname, T * ptr, + void (T::*mptr)(value_t& result, scope_t * locals)) { + return wrap_functor(new memfun_args_functor_t(fname, ptr, mptr)); + } + +#define MAKE_FUNCTOR(cls, name) \ + xml::xpath_t::make_functor(#name, this, &cls::name) + +public: + class scope_t + { + typedef std::map symbol_map; + typedef std::pair symbol_pair; + + symbol_map symbols; + + scope_t(const scope_t&); + scope_t& operator=(const scope_t&); + + public: + scope_t * parent; + value_t args; + + enum kind_t { NORMAL, STATIC, ARGUMENT } kind; + + scope_t(scope_t * _parent = NULL, kind_t _kind = NORMAL) + : parent(_parent), kind(_kind) { + TRACE_CTOR(xpath_t::scope_t, "scope *, kind_t"); + } + + virtual ~scope_t() { + TRACE_DTOR(xpath_t::scope_t); + for (symbol_map::iterator i = symbols.begin(); + i != symbols.end(); + i++) + (*i).second->release(); + } + + public: + virtual void define(const string& name, op_t * def); + virtual bool resolve(const string& name, value_t& result, + scope_t * locals = NULL) { + if (parent) + return parent->resolve(name, result, locals); + return false; + } + virtual op_t * lookup(const string& name); + + void define(const string& name, functor_t * def); + + friend struct op_t; + }; + + class function_scope_t : public scope_t + { + value_t::sequence_t * sequence; + value_t * value; + int index; + + public: + function_scope_t(value_t::sequence_t * _sequence, value_t * _value, + int _index, scope_t * _parent = NULL) + : scope_t(_parent, STATIC), + sequence(_sequence), value(_value), index(_index) {} + + virtual bool resolve(const string& name, value_t& result, + scope_t * locals = NULL); + }; + +#define XPATH_PARSE_NORMAL 0x00 +#define XPATH_PARSE_PARTIAL 0x01 +#define XPATH_PARSE_RELAXED 0x02 +#define XPATH_PARSE_NO_MIGRATE 0x04 +#define XPATH_PARSE_NO_REDUCE 0x08 +#if 0 +#define XPATH_PARSE_REGEXP 0x10 +#endif +#define XPATH_PARSE_ALLOW_DATE 0x20 + +private: + struct token_t + { + enum kind_t { + IDENT, // [A-Za-z_][-A-Za-z0-9_:]* + VALUE, // any kind of literal value +#if 0 + REGEXP, // /regexp/ jww (2006-09-24): deprecate + // in favor of a "match" function +#endif + AT_SYM, // @ + DOLLAR, // $ + DOT, // . + DOTDOT, // .. + LPAREN, // ( + RPAREN, // ) + LBRACKET, // ( + RBRACKET, // ) + EXCLAM, // ! + NEQUAL, // != + MINUS, // - + PLUS, // + + STAR, // * + POWER, // ** + SLASH, // / + EQUAL, // = + ASSIGN, // := + LESS, // < + LESSEQ, // <= + GREATER, // > + GREATEREQ, // >= + AMPER, // & + PIPE, // | + QUESTION, // ? + COLON, // : + COMMA, // , +#if 0 + MATCH, // =~ + NMATCH, // !~ + PERCENT, // % +#endif + KW_AND, + KW_OR, + KW_DIV, + KW_MOD, + KW_UNION, + TOK_EOF, + UNKNOWN + } kind; + + char symbol[3]; + value_t value; + unsigned int length; + + token_t() : kind(UNKNOWN), length(0) { + TRACE_CTOR(xpath_t::token_t, ""); + } + + token_t(const token_t& other) { + assert(0); + TRACE_CTOR(xpath_t::token_t, "copy"); + *this = other; + } + + ~token_t() { + TRACE_DTOR(xpath_t::token_t); + } + + token_t& operator=(const token_t& other) { + if (&other == this) + return *this; + assert(0); + return *this; + } + + void clear() { + kind = UNKNOWN; + length = 0; + value = 0L; + + symbol[0] = '\0'; + symbol[1] = '\0'; + symbol[2] = '\0'; + } + + void parse_ident(std::istream& in); + void next(std::istream& in, unsigned short flags); + void rewind(std::istream& in); + void unexpected(); + + static void unexpected(char c, char wanted = '\0'); + }; + +public: + struct op_t + { + enum kind_t { + VOID, + VALUE, + + NODE_NAME, + NODE_ID, + FUNC_NAME, + ATTR_NAME, + VAR_NAME, + + ARG_INDEX, + + CONSTANTS, // constants end here + + FUNCTOR, +#if 0 + MASK, +#endif + + TERMINALS, // terminals end here + + O_NOT, + O_NEG, + + O_UNION, + + O_ADD, + O_SUB, + O_MUL, + O_DIV, + + O_NEQ, + O_EQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + + O_AND, + O_OR, + + O_QUES, + O_COLON, + + O_COMMA, + +#if 0 + O_MATCH, + O_NMATCH, +#endif + + O_DEFINE, + O_EVAL, + O_ARG, + +#if 0 + O_PERC, +#endif + + O_FIND, + O_RFIND, + O_PRED, + + LAST // operators end here + }; + + kind_t kind; + mutable short refc; + op_t * left; + + union { + value_t * valuep; // used by constant VALUE + string * name; // used by constant SYMBOL + unsigned int arg_index; // used by ARG_INDEX and O_ARG + functor_t * functor; // used by terminal FUNCTOR + unsigned int name_id; // used by NODE_NAME and ATTR_NAME +#if 0 + mask_t * mask; // used by terminal MASK +#endif + op_t * right; // used by all operators + }; + + op_t(const kind_t _kind) + : kind(_kind), refc(0), left(NULL), right(NULL) { + TRACE_CTOR(xpath_t::op_t, "const kind_t"); + } + op_t(const op_t&); + ~op_t(); + + op_t& operator=(const op_t&); + + bool constant() const { + return kind == VALUE; + } + void get_value(value_t& result) const; + value_t value() const { + value_t temp; + get_value(temp); + return temp; + } + + functor_t * functor_obj() const { + if (kind == FUNCTOR) + return functor; + else + return NULL; + } + + void release() const { + DEBUG_("ledger.xpath.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + delete this; + } + op_t * acquire() { + DEBUG_("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + const op_t * acquire() const { + DEBUG_("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + + void set_left(op_t * expr) { + assert(kind > TERMINALS); + if (left) + left->release(); + left = expr ? expr->acquire() : NULL; + } + + void set_right(op_t * expr) { + assert(kind > TERMINALS); + if (right) + right->release(); + right = expr ? expr->acquire() : NULL; + } + + static op_t * new_node(kind_t kind, op_t * left = NULL, + op_t * right = NULL); + + op_t * copy(op_t * left = NULL, + op_t * right = NULL) const; + op_t * compile(value_t * context, scope_t * scope, + bool resolve = false); + + void find_values(value_t * context, scope_t * scope, + value_t::sequence_t& result_seq, bool recursive); + bool test_value(value_t * context, scope_t * scope, int index = 0); + + void append_value(value_t& value, value_t::sequence_t& result_seq); + + static op_t * defer_sequence(value_t::sequence_t& result_seq); + + bool write(std::ostream& out, + const bool relaxed = true, + const op_t * op_to_find = NULL, + unsigned long * start_pos = NULL, + unsigned long * end_pos = NULL) const; + + void dump(std::ostream& out, const int depth) const; + }; + +public: + op_t * ptr; + + xpath_t& operator=(op_t * _expr) { + expr = ""; + reset(_expr); + return *this; + } + + op_t& operator*() throw() { + return *ptr; + } + const op_t& operator*() const throw() { + return *ptr; + } + op_t * operator->() throw() { + return ptr; + } + const op_t * operator->() const throw() { + return ptr; + } + + op_t * get() throw() { return ptr; } + const op_t * get() const throw() { return ptr; } + + op_t * release() throw() { + op_t * tmp = ptr; + ptr = 0; + return tmp; + } + + void reset(op_t * p = 0) throw() { + if (p != ptr) { + if (ptr) + ptr->release(); + ptr = p; + } + } + +#ifdef THREADSAFE + mutable token_t lookahead; +#else + static token_t * lookahead; +#endif + mutable bool use_lookahead; + + token_t& next_token(std::istream& in, unsigned short tflags) const { + if (use_lookahead) + use_lookahead = false; + else +#ifdef THREADSAFE + lookahead.next(in, tflags); +#else + lookahead->next(in, tflags); +#endif +#ifdef THREADSAFE + return lookahead; +#else + return *lookahead; +#endif + } + void push_token(const token_t& tok) const { +#ifdef THREADSAFE + assert(&tok == &lookahead); +#else + assert(&tok == lookahead); +#endif + use_lookahead = true; + } + void push_token() const { + use_lookahead = true; + } + + op_t * parse_value_term(std::istream& in, unsigned short flags) const; + op_t * parse_predicate_expr(std::istream& in, unsigned short flags) const; + op_t * parse_path_expr(std::istream& in, unsigned short flags) const; + op_t * parse_unary_expr(std::istream& in, unsigned short flags) const; + op_t * parse_union_expr(std::istream& in, unsigned short flags) const; + op_t * parse_mul_expr(std::istream& in, unsigned short flags) const; + op_t * parse_add_expr(std::istream& in, unsigned short flags) const; + op_t * parse_logic_expr(std::istream& in, unsigned short flags) const; + op_t * parse_and_expr(std::istream& in, unsigned short flags) const; + op_t * parse_or_expr(std::istream& in, unsigned short flags) const; + op_t * parse_querycolon_expr(std::istream& in, unsigned short flags) const; + op_t * parse_value_expr(std::istream& in, unsigned short flags) const; + + op_t * parse_expr(std::istream& in, + unsigned short flags = XPATH_PARSE_RELAXED) const; + + op_t * parse_expr(const string& str, + unsigned short tflags = XPATH_PARSE_RELAXED) const + { + std::istringstream stream(str); +#if 0 + try { +#endif + return parse_expr(stream, tflags); +#if 0 + } + catch (error * err) { + err->context.push_back + (new line_context(str, (long)stream.tellg() - 1, + "While parsing value expression:")); + throw err; + } +#endif + } + + op_t * parse_expr(const char * p, + unsigned short tflags = XPATH_PARSE_RELAXED) const { + return parse_expr(string(p), tflags); + } + + bool write(std::ostream& out, + const bool relaxed, + const op_t * op_to_find, + unsigned long * start_pos, + unsigned long * end_pos) const { + if (ptr) + ptr->write(out, relaxed, op_to_find, start_pos, end_pos); + return true; + } + +public: + string expr; + unsigned short flags; // flags used to parse `expr' + + xpath_t() : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR(xpath_t, ""); + } + xpath_t(op_t * _ptr) : ptr(_ptr), use_lookahead(false) { + TRACE_CTOR(xpath_t, "op_t *"); + } + + xpath_t(const string& _expr, + unsigned short _flags = XPATH_PARSE_RELAXED) + : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR(xpath_t, "const string&, unsigned short"); + if (! _expr.empty()) + parse(_expr, _flags); + } + xpath_t(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) + : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR(xpath_t, "std::istream&, unsigned short"); + parse(in, _flags); + } + xpath_t(const xpath_t& other) + : ptr(other.ptr ? other.ptr->acquire() : NULL), + use_lookahead(false), expr(other.expr), flags(other.flags) { + TRACE_CTOR(xpath_t, "copy"); + } + virtual ~xpath_t() { + TRACE_DTOR(xpath_t); + reset(NULL); + } + + xpath_t& operator=(const string& _expr) { + parse(_expr); + return *this; + } + xpath_t& operator=(const xpath_t& _expr); + xpath_t& operator=(xpath_t& _xpath) { + ptr = _xpath.ptr->acquire(); + expr = _xpath.expr; + flags = _xpath.flags; + use_lookahead = false; + return *this; + } + + operator op_t *() throw() { + return ptr; + } + + operator bool() const throw() { + return ptr != NULL; + } + operator string() const throw() { + return expr; + } + + void parse(const string& _expr, unsigned short _flags = XPATH_PARSE_RELAXED) { + expr = _expr; + flags = _flags; + op_t * tmp = parse_expr(_expr, _flags); + assert(tmp); + reset(tmp ? tmp->acquire() : NULL); + } + void parse(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) { + expr = ""; + flags = _flags; + op_t * tmp = parse_expr(in, _flags); + assert(tmp); + reset(tmp ? tmp->acquire() : NULL); + } + + void compile(const string& _expr, scope_t * scope = NULL, + unsigned short _flags = XPATH_PARSE_RELAXED) { + parse(_expr, _flags); + // jww (2006-09-24): fix + compile((node_t *)NULL, scope); + } + void compile(std::istream& in, scope_t * scope = NULL, + unsigned short _flags = XPATH_PARSE_RELAXED) { + parse(in, _flags); + // jww (2006-09-24): fix + compile((node_t *)NULL, scope); + } + + void compile(document_t * document, scope_t * scope = NULL) { + if (! document) { + document_t tdoc; + compile(tdoc.top, scope); + } else { + compile(document->top, scope); + } + } + void compile(node_t * top_node, scope_t * scope = NULL) { + if (ptr) { + value_t noderef(top_node); + op_t * compiled = ptr->compile(&noderef, scope); + if (compiled == ptr) + compiled->release(); + else + reset(compiled); + } + } + + virtual void calc(value_t& result, node_t * node, scope_t * scope = NULL) const; + + virtual value_t calc(document_t * document, scope_t * scope = NULL) const { + if (! ptr) + return 0L; + value_t temp; + calc(temp, document ? document->top : NULL, scope); + return temp; + } + virtual value_t calc(node_t * tcontext, scope_t * scope = NULL) const { + if (! ptr) + return 0L; + value_t temp; + calc(temp, tcontext, scope); + return temp; + } + + static void eval(value_t& result, const string& _expr, + document_t * document, scope_t * scope = NULL) { + xpath_t temp(_expr); + temp.calc(result, document->top, scope); + } + static value_t eval(const string& _expr, document_t * document, + scope_t * scope = NULL) { + xpath_t temp(_expr); + return temp.calc(document, scope); + } + + void write(std::ostream& out) const { + write(out, true, NULL, NULL, NULL); + } + void dump(std::ostream& out) const { + if (ptr) + ptr->dump(out, 0); + } + + friend class scope_t; +}; + +inline std::ostream& operator<<(std::ostream& out, const xpath_t::op_t& op) { + op.write(out); + return out; +}; + +} // namespace xml + +template +inline T * get_ptr(xml::xpath_t::scope_t * locals, unsigned int idx) { + assert(locals->args.size() > idx); + T * ptr = static_cast(locals->args[idx].to_pointer()); + assert(ptr); + return ptr; +} + +class xml_command : public xml::xpath_t::functor_t +{ + public: + xml_command() : xml::xpath_t::functor_t("xml") {} + + virtual void operator()(value_t&, xml::xpath_t::scope_t * locals) { + std::ostream * out = get_ptr(locals, 0); + xml::document_t * doc = get_ptr(locals, 1); + + doc->write(*out); + } +}; + +} // namespace ledger + +#endif // _XPATH_H diff --git a/system.hh b/system.hh deleted file mode 100644 index a61e168e..00000000 --- a/system.hh +++ /dev/null @@ -1,105 +0,0 @@ -#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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; - } -} -#endif - -#include -#include -#include -#include -#include -#include -#include - -#if defined __FreeBSD__ && __FreeBSD__ <= 4 -// FreeBSD has a broken isspace macro, so don't use it -#undef isspace(c) -#endif - -#include - -#ifdef HAVE_UNIX_PIPES -#include -#include -#include "fdstream.hpp" -#endif - -#ifdef WIN32 -#include -#else -#include -#endif - -#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) -#include -#endif - -#if defined(HAVE_NL_LANGINFO) -#include -#endif - -#include - -#define HAVE_GDTOA 1 -#ifdef HAVE_GDTOA -#include "gdtoa/gdtoa.h" -#endif - -extern "C" { -#if defined(HAVE_EXPAT) -#include // expat XML parser -#elif defined(HAVE_XMLPARSE) -#include // expat XML parser -#endif -} - -#if defined(HAVE_LIBOFX) -#include -#endif - -#endif // _SYSTEM_HH diff --git a/tests/corelib/numerics/BasicAmount.cc b/tests/corelib/numerics/BasicAmount.cc deleted file mode 100644 index 90cf26ec..00000000 --- a/tests/corelib/numerics/BasicAmount.cc +++ /dev/null @@ -1,625 +0,0 @@ -#include "BasicAmount.h" - -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BasicAmountTestCase, "numerics"); - -void BasicAmountTestCase::setUp() { - ledger::initialize(); -} -void BasicAmountTestCase::tearDown() { - ledger::shutdown(); -} - -void BasicAmountTestCase::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(std::string("123456")); - amount_t x8(std::string("123.456")); - amount_t x9(x3); - amount_t x10(x6); - amount_t x11(x8); - - assertEqual(amount_t(0L), x0); - assertEqual(amount_t(), x0); - assertEqual(amount_t("0"), x0); - assertEqual(amount_t("0.0"), x0); - assertEqual(x2, x1); - assertEqual(x5, x1); - assertEqual(x7, x1); - assertEqual(x6, x3); - assertEqual(x8, x3); - assertEqual(x10, x3); - assertEqual(x10, x9); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x5.valid()); - CPPUNIT_ASSERT(x6.valid()); - CPPUNIT_ASSERT(x7.valid()); - CPPUNIT_ASSERT(x8.valid()); - CPPUNIT_ASSERT(x9.valid()); - CPPUNIT_ASSERT(x10.valid()); - CPPUNIT_ASSERT(x11.valid()); -} - -void BasicAmountTestCase::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(std::string("-123456")); - amount_t x8(std::string("-123.456")); - amount_t x9(- x3); - - assertEqual(amount_t(0L), x0); - 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); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x5.valid()); - CPPUNIT_ASSERT(x6.valid()); - CPPUNIT_ASSERT(x7.valid()); - CPPUNIT_ASSERT(x8.valid()); - CPPUNIT_ASSERT(x9.valid()); - CPPUNIT_ASSERT(x10.valid()); -} - -void BasicAmountTestCase::testAssignment() -{ - 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 = amount_t(x6); - - assertEqual(amount_t(0L), x0); - assertEqual(x2, x1); - assertEqual(x5, x1); - assertEqual(x7, x1); - assertEqual(x6, x3); - assertEqual(x8, x3); - assertEqual(x10, x3); - assertEqual(x10, x9); - - x0 = amount_t(); - x1 = 123456L; - x2 = 123456UL; - x3 = 123.456; - x5 = "123456"; - x6 = "123.456"; - x7 = std::string("123456"); - x8 = std::string("123.456"); - x9 = x3; - x10 = amount_t(x6); - - assertEqual(amount_t(0L), x0); - assertEqual(x2, x1); - assertEqual(x5, x1); - assertEqual(x7, x1); - assertEqual(x6, x3); - assertEqual(x8, x3); - assertEqual(x10, x3); - assertEqual(x10, x9); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x5.valid()); - CPPUNIT_ASSERT(x6.valid()); - CPPUNIT_ASSERT(x7.valid()); - CPPUNIT_ASSERT(x8.valid()); - CPPUNIT_ASSERT(x9.valid()); - CPPUNIT_ASSERT(x10.valid()); -} - -void BasicAmountTestCase::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.0F); - - CPPUNIT_ASSERT(x1 == 123456L); - CPPUNIT_ASSERT(x1 != x2); - CPPUNIT_ASSERT(x1 == (x2 - x3)); - CPPUNIT_ASSERT(x1 == x4); - CPPUNIT_ASSERT(x4 == x5); - CPPUNIT_ASSERT(x4 == x6); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x4.valid()); - CPPUNIT_ASSERT(x5.valid()); - CPPUNIT_ASSERT(x6.valid()); -} - -void BasicAmountTestCase::testIntegerAddition() -{ - 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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x4.valid()); -} - -void BasicAmountTestCase::testFractionalAddition() -{ - amount_t x1(123.123); - amount_t y1(456.456); - - assertEqual(amount_t(579.579), x1 + y1); - assertEqual(amount_t(579.579), x1 + 456.456); - assertEqual(amount_t(579.579), 456.456 + x1); - - x1 += amount_t(456.456); - assertEqual(amount_t(579.579), x1); - x1 += 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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x2.valid()); -} - -void BasicAmountTestCase::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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x4.valid()); - CPPUNIT_ASSERT(y4.valid()); -} - -void BasicAmountTestCase::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 -= 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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(y2.valid()); -} - -void BasicAmountTestCase::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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x4.valid()); -} - -void BasicAmountTestCase::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 * 456.456); - assertEqual(amount_t("56200.232088"), amount_t(456.456) * x1); - assertEqual(amount_t("56200.232088"), 456.456 * x1); - - x1 *= amount_t(123.123); - assertEqual(amount_t("15159.273129"), x1); - x1 *= 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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x2.valid()); -} - -void BasicAmountTestCase::testIntegerDivision() -{ - amount_t x1(123L); - amount_t y1(456L); - - assertThrow(x1 / 0L, amount_exception); - 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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x4.valid()); - CPPUNIT_ASSERT(y4.valid()); -} - -void BasicAmountTestCase::testFractionalDivision() -{ - amount_t x1(123.123); - amount_t y1(456.456); - - assertThrow(x1 / 0L, amount_exception); - assertEqual(amount_t("0.008121959"), amount_t(1.0) / x1); - assertEqual(amount_t("0.008121959"), 1.0 / x1); - assertEqual(x1, x1 / 1.0); - assertEqual(amount_t("0.008121959"), amount_t(1.0) / x1); - assertEqual(amount_t("0.008121959"), 1.0 / x1); - assertEqual(- x1, x1 / -1.0); - assertEqual(- amount_t("0.008121959"), amount_t(-1.0) / x1); - assertEqual(- amount_t("0.008121959"), -1.0 / x1); - assertEqual(amount_t("0.269736842105263"), x1 / y1); - assertEqual(amount_t("3.707317073170732"), y1 / x1); - assertEqual(amount_t("0.269736842105263"), x1 / 456.456); - assertEqual(amount_t("3.707317073170732"), amount_t(456.456) / x1); - assertEqual(amount_t("3.707317073170732"), 456.456 / x1); - - x1 /= amount_t(456.456); - assertEqual(amount_t("0.269736842105263"), x1); - x1 /= 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); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(y1.valid()); - CPPUNIT_ASSERT(x4.valid()); - CPPUNIT_ASSERT(y4.valid()); -} - -void BasicAmountTestCase::testIntegerConversion() -{ - amount_t x1(123456L); - - assertEqual(true, bool(x1)); - assertEqual(123456L, long(x1)); - assertEqual(123456.0, double(x1)); - assertEqual(string("123456"), x1.to_string()); - assertEqual(string("123456"), x1.quantity_string()); - - CPPUNIT_ASSERT(x1.valid()); -} - -void BasicAmountTestCase::testFractionalConversion() -{ - amount_t x1(1234.56); - - assertEqual(true, bool(x1)); - assertEqual(1234L, long(x1)); - assertEqual(1234.56, double(x1)); - assertEqual(string("1234.56"), x1.to_string()); - assertEqual(string("1234.56"), x1.quantity_string()); - - CPPUNIT_ASSERT(x1.valid()); -} - -void BasicAmountTestCase::testFractionalRound() -{ - amount_t x1("1234.567890"); - - assertEqual(amount_t("1234.56789"), x1.round(6)); - assertEqual(amount_t("1234.56789"), x1.round(5)); - assertEqual(amount_t("1234.5679"), x1.round(4)); - assertEqual(amount_t("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("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(), x5.round(36)); - - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x4.valid()); -} - -void BasicAmountTestCase::testTruth() -{ - amount_t x0; - amount_t x1("1234"); - amount_t x2("1234.56"); - - if (x0) - CPPUNIT_ASSERT(false); - else - CPPUNIT_ASSERT(true); - - if (x1) - CPPUNIT_ASSERT(true); - else - CPPUNIT_ASSERT(false); - - if (x2) - CPPUNIT_ASSERT(true); - else - CPPUNIT_ASSERT(false); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); -} - -void BasicAmountTestCase::testForZero() -{ - amount_t x0; - amount_t x1("0.000000000000000000001"); - - CPPUNIT_ASSERT(! x0); - CPPUNIT_ASSERT(x1); - CPPUNIT_ASSERT(x0.zero()); - CPPUNIT_ASSERT(x0.realzero()); - CPPUNIT_ASSERT(! x1.zero()); - CPPUNIT_ASSERT(! x1.realzero()); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); -} - -void BasicAmountTestCase::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"); - - CPPUNIT_ASSERT(x0 > x1); - CPPUNIT_ASSERT(x0 < x2); - CPPUNIT_ASSERT(x0 > x3); - CPPUNIT_ASSERT(x0 < x4); - CPPUNIT_ASSERT(x0 > x5); - CPPUNIT_ASSERT(x0 < x6); - - CPPUNIT_ASSERT(x1 > x3); - CPPUNIT_ASSERT(x3 <= x5); - CPPUNIT_ASSERT(x3 >= x5); - CPPUNIT_ASSERT(x3 < x1); - CPPUNIT_ASSERT(x3 < x4); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x4.valid()); - CPPUNIT_ASSERT(x5.valid()); - CPPUNIT_ASSERT(x6.valid()); -} - -void BasicAmountTestCase::testSign() -{ - amount_t x0; - amount_t x1("0.0000000000000000000000000000000000001"); - amount_t x2("-0.0000000000000000000000000000000000001"); - amount_t x3("1"); - amount_t x4("-1"); - - CPPUNIT_ASSERT(! x0.sign()); - CPPUNIT_ASSERT(x1.sign() > 0); - CPPUNIT_ASSERT(x2.sign() < 0); - CPPUNIT_ASSERT(x3.sign() > 0); - CPPUNIT_ASSERT(x4.sign() < 0); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); - CPPUNIT_ASSERT(x3.valid()); - CPPUNIT_ASSERT(x4.valid()); -} - -void BasicAmountTestCase::testAbs() -{ - amount_t x0; - amount_t x1(-1234L); - amount_t x2(1234L); - - assertEqual(amount_t(), x0.abs()); - assertEqual(amount_t(1234L), x1.abs()); - assertEqual(amount_t(1234L), x2.abs()); - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); - CPPUNIT_ASSERT(x2.valid()); -} - -void BasicAmountTestCase::testPrinting() -{ - amount_t x0; - amount_t x1("982340823.380238098235098235098235098"); - - { - std::ostringstream bufstr; - bufstr << x0; - - assertEqual(std::string("0"), bufstr.str()); - } - - { - std::ostringstream bufstr; - bufstr << x1; - - assertEqual(std::string("982340823.380238098235098235098235098"), - bufstr.str()); - } - - CPPUNIT_ASSERT(x0.valid()); - CPPUNIT_ASSERT(x1.valid()); -} diff --git a/tests/corelib/numerics/BasicAmount.h b/tests/corelib/numerics/BasicAmount.h deleted file mode 100644 index 2c107f45..00000000 --- a/tests/corelib/numerics/BasicAmount.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef _BASICAMOUNT_H -#define _BASICAMOUNT_H - -#include "UnitTests.h" - -class BasicAmountTestCase : public CPPUNIT_NS::TestCase -{ - CPPUNIT_TEST_SUITE(BasicAmountTestCase); - - CPPUNIT_TEST(testConstructors); - CPPUNIT_TEST(testNegation); - CPPUNIT_TEST(testAssignment); - CPPUNIT_TEST(testEquality); - CPPUNIT_TEST(testIntegerAddition); - CPPUNIT_TEST(testFractionalAddition); - CPPUNIT_TEST(testIntegerSubtraction); - CPPUNIT_TEST(testFractionalSubtraction); - CPPUNIT_TEST(testIntegerMultiplication); - CPPUNIT_TEST(testFractionalMultiplication); - CPPUNIT_TEST(testIntegerDivision); - CPPUNIT_TEST(testFractionalDivision); - CPPUNIT_TEST(testIntegerConversion); - CPPUNIT_TEST(testFractionalConversion); - CPPUNIT_TEST(testFractionalRound); - CPPUNIT_TEST(testTruth); - CPPUNIT_TEST(testForZero); - CPPUNIT_TEST(testComparisons); - CPPUNIT_TEST(testSign); - CPPUNIT_TEST(testAbs); - CPPUNIT_TEST(testPrinting); - - CPPUNIT_TEST_SUITE_END(); - -public: - BasicAmountTestCase() {} - virtual ~BasicAmountTestCase() {} - - virtual void setUp(); - virtual void tearDown(); - - void testConstructors(); - void testNegation(); - void testAssignment(); - void testEquality(); - void testIntegerAddition(); - void testFractionalAddition(); - void testIntegerSubtraction(); - void testFractionalSubtraction(); - void testIntegerMultiplication(); - void testFractionalMultiplication(); - void testIntegerDivision(); - void testFractionalDivision(); - void testIntegerConversion(); - void testFractionalConversion(); - void testFractionalRound(); - void testTruth(); - void testForZero(); - void testComparisons(); - void testSign(); - void testAbs(); - void testPrinting(); - -private: - BasicAmountTestCase(const BasicAmountTestCase ©); - void operator=(const BasicAmountTestCase ©); -}; - -#endif /* _BASICAMOUNT_H */ diff --git a/tests/corelib/numerics/Commodity.cc b/tests/corelib/numerics/Commodity.cc deleted file mode 100644 index e7e2a18c..00000000 --- a/tests/corelib/numerics/Commodity.cc +++ /dev/null @@ -1,55 +0,0 @@ -#include "Commodity.h" - -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommodityTestCase, "numerics"); - -void CommodityTestCase::setUp() { - ledger::initialize(); -} -void CommodityTestCase::tearDown() { - ledger::shutdown(); -} - -void CommodityTestCase::testPriceHistory() -{ - ptime jan17_07 = parse_datetime("2007/01/17 00:00:00"); - ptime feb27_07 = parse_datetime("2007/02/27 18:00:00"); - ptime feb28_07 = parse_datetime("2007/02/28 06:00:00"); - ptime feb28_07sbm = parse_datetime("2007/02/28 11:59:59"); - ptime mar01_07 = parse_datetime("2007/03/01 00:00:00"); - ptime apr15_07 = parse_datetime("2007/04/15 13:00:00"); - - // jww (2007-04-17): tbd - amount_t x1("100.10 AAPL"); - - // 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")); - - assertEqual(amount_t("$1831.83"), x1.value(feb28_07sbm)); - assertEqual(amount_t("$2124.12"), x1.value(now)); - - 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/tests/corelib/numerics/Commodity.h b/tests/corelib/numerics/Commodity.h deleted file mode 100644 index dafa03e9..00000000 --- a/tests/corelib/numerics/Commodity.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef _COMMMODITY_H -#define _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: - CommodityTestCase() {} - virtual ~CommodityTestCase() {} - - virtual void setUp(); - virtual void tearDown(); - - void testPriceHistory(); - void testLots(); - void testScalingBase(); - void testReduction(); - -private: - CommodityTestCase(const CommodityTestCase ©); - void operator=(const CommodityTestCase ©); -}; - -#endif /* _COMMMODITY_H */ diff --git a/tests/corelib/numerics/CommodityAmount.cc b/tests/corelib/numerics/CommodityAmount.cc deleted file mode 100644 index 7ed7f403..00000000 --- a/tests/corelib/numerics/CommodityAmount.cc +++ /dev/null @@ -1,696 +0,0 @@ -#include "CommodityAmount.h" - -#define internalAmount(x) amount_t::exact(x) - -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommodityAmountTestCase, "numerics"); - -void CommodityAmountTestCase::setUp() -{ - ledger::initialize(); - - // Cause the display precision for dollars to be initialized to 2. - amount_t x1("$1.00"); - assertTrue(x1); - amount_t::full_strings = true; // makes error reports from UnitTests accurate -} - -void CommodityAmountTestCase::tearDown() -{ - amount_t::full_strings = false; - ledger::shutdown(); -} - -void CommodityAmountTestCase::testConstructors() -{ - 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 CommodityAmountTestCase::testNegation() -{ - 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 CommodityAmountTestCase::testAssignment() -{ - 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 CommodityAmountTestCase::testEquality() -{ - amount_t x0; - 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€"; - - assertTrue(x0.null()); - assertTrue(x0.zero()); - assertTrue(x0.realzero()); - assertTrue(x0.sign() == 0); - assertTrue(x0.compare(x1) < 0); - assertTrue(x0.compare(x2) > 0); - assertTrue(x0.compare(x0) == 0); - - 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 CommodityAmountTestCase::testAddition() -{ - 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_exception); - assertThrow(x1 + x3, amount_exception); - assertThrow(x1 + x4, amount_exception); - assertThrow(x1 + x5, amount_exception); - assertThrow(x1 + x6, amount_exception); - assertThrow(x1 + 123.45, amount_exception); - assertThrow(x1 + 123L, amount_exception); - - 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 CommodityAmountTestCase::testSubtraction() -{ - 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_exception); - assertThrow(x1 - x3, amount_exception); - assertThrow(x1 - x4, amount_exception); - assertThrow(x1 - x5, amount_exception); - assertThrow(x1 - x6, amount_exception); - assertThrow(x1 - 123.45, amount_exception); - assertThrow(x1 - 123L, amount_exception); - - 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 CommodityAmountTestCase::testMultiplication() -{ - 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 * x3, amount_exception); - assertThrow(x1 * x4, amount_exception); - assertThrow(x1 * x5, amount_exception); - - x1 *= amount_t("123.12"); - assertEqual(internalAmount("$15158.5344"), x1); - assertEqual(string("$15158.53"), x1.to_string()); - x1 *= 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 CommodityAmountTestCase::testDivision() -{ - 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_exception); - 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 / x3, amount_exception); - assertThrow(x1 / x4, amount_exception); - assertThrow(x1 / x5, amount_exception); - - x1 /= amount_t("123.12"); - assertEqual(internalAmount("$1.00"), x1); - assertEqual(string("$1.00"), x1.to_string()); - x1 /= 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 CommodityAmountTestCase::testConversion() -{ - amount_t x1("$1234.56"); - - assertEqual(true, bool(x1)); - assertEqual(1234L, long(x1)); - assertEqual(1234.56, double(x1)); - assertEqual(string("$1234.56"), x1.to_string()); - assertEqual(string("1234.56"), x1.quantity_string()); - - assertValid(x1); -} - -void CommodityAmountTestCase::testRound() -{ - 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 *= 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 CommodityAmountTestCase::testDisplayRound() -{ - amount_t x1("$0.85"); - amount_t x2("$0.1"); - - x1 *= 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 CommodityAmountTestCase::testTruth() -{ - amount_t x1("$1234"); - amount_t x2("$1234.56"); - - if (x1) - CPPUNIT_ASSERT(true); - else - CPPUNIT_ASSERT(false); - - if (x2) - CPPUNIT_ASSERT(true); - else - CPPUNIT_ASSERT(false); - - assertValid(x1); - assertValid(x2); -} - -void CommodityAmountTestCase::testForZero() -{ - amount_t x1(internalAmount("$0.000000000000000000001")); - - assertFalse(x1); - assertTrue(x1.zero()); - assertFalse(x1.realzero()); - - assertValid(x1); -} - -void CommodityAmountTestCase::testComparisons() -{ - amount_t x0; - 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"); - - assertTrue(x0 > x1); - assertTrue(x0 < x2); - assertTrue(x0 > x3); - assertTrue(x0 < x4); - assertTrue(x0 > x5); - assertTrue(x0 < x6); - - assertTrue(x1 > x3); - assertTrue(x3 <= x5); - assertTrue(x3 < x5); - assertTrue(x3 <= x5); - assertFalse(x3 == x5); - assertTrue(x3 < x1); - assertTrue(x3 < x4); - - assertValid(x0); - assertValid(x1); - assertValid(x2); - assertValid(x3); - assertValid(x4); - assertValid(x5); - assertValid(x6); -} - -void CommodityAmountTestCase::testSign() -{ - amount_t x0; - amount_t x1(internalAmount("$0.0000000000000000000000000000000000001")); - amount_t x2(internalAmount("$-0.0000000000000000000000000000000000001")); - amount_t x3("$1"); - amount_t x4("$-1"); - - assertFalse(x0.sign()); - 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 CommodityAmountTestCase::testAbs() -{ - amount_t x0; - amount_t x1("$-1234.56"); - amount_t x2("$1234.56"); - - assertEqual(amount_t(), x0.abs()); - assertEqual(amount_t("$1234.56"), x1.abs()); - assertEqual(amount_t("$1234.56"), x2.abs()); - - assertValid(x0); - assertValid(x1); - assertValid(x2); -} - -void CommodityAmountTestCase::testPrinting() -{ - amount_t x0; - amount_t x1(internalAmount("$982340823.386238098235098235098235098")); - amount_t x2("$982340823.38"); - - { - std::ostringstream bufstr; - bufstr << x0; - - assertEqual(std::string("0"), bufstr.str()); - } - - { - 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(x0); - assertValid(x1); - assertValid(x2); -} - diff --git a/tests/corelib/numerics/CommodityAmount.h b/tests/corelib/numerics/CommodityAmount.h deleted file mode 100644 index 5ffa7810..00000000 --- a/tests/corelib/numerics/CommodityAmount.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef _COMMODITYAMOUNT_H -#define _COMMODITYAMOUNT_H - -#include "UnitTests.h" - -class CommodityAmountTestCase : public CPPUNIT_NS::TestCase -{ - CPPUNIT_TEST_SUITE(CommodityAmountTestCase); - - CPPUNIT_TEST(testConstructors); - CPPUNIT_TEST(testNegation); - CPPUNIT_TEST(testAssignment); - CPPUNIT_TEST(testEquality); - CPPUNIT_TEST(testAddition); - CPPUNIT_TEST(testSubtraction); - CPPUNIT_TEST(testMultiplication); - CPPUNIT_TEST(testDivision); - CPPUNIT_TEST(testConversion); - CPPUNIT_TEST(testRound); - CPPUNIT_TEST(testDisplayRound); - CPPUNIT_TEST(testTruth); - CPPUNIT_TEST(testForZero); - CPPUNIT_TEST(testComparisons); - CPPUNIT_TEST(testSign); - CPPUNIT_TEST(testAbs); - CPPUNIT_TEST(testPrinting); - - CPPUNIT_TEST_SUITE_END(); - -public: - CommodityAmountTestCase() {} - virtual ~CommodityAmountTestCase() {} - - virtual void setUp(); - virtual void tearDown(); - - void testConstructors(); - void testNegation(); - void testAssignment(); - void testEquality(); - void testAddition(); - void testSubtraction(); - void testMultiplication(); - void testDivision(); - void testConversion(); - void testRound(); - void testDisplayRound(); - void testTruth(); - void testForZero(); - void testComparisons(); - void testSign(); - void testAbs(); - void testPrinting(); - -private: - CommodityAmountTestCase(const CommodityAmountTestCase ©); - void operator=(const CommodityAmountTestCase ©); -}; - -#endif /* _COMMODITYAMOUNT_H */ diff --git a/tests/corelib/numerics/DateTime.cc b/tests/corelib/numerics/DateTime.cc deleted file mode 100644 index 24b8dd16..00000000 --- a/tests/corelib/numerics/DateTime.cc +++ /dev/null @@ -1,79 +0,0 @@ -#include "DateTimeTest.h" - -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DateTimeTestCase, "numerics"); - -void DateTimeTestCase::setUp() {} -void DateTimeTestCase::tearDown() {} - -void DateTimeTestCase::testConstructors() -{ - 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 -} diff --git a/tests/corelib/numerics/DateTimeTest.h b/tests/corelib/numerics/DateTimeTest.h deleted file mode 100644 index abc914cd..00000000 --- a/tests/corelib/numerics/DateTimeTest.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef _DATETIMETEST_H -#define _DATETIMETEST_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 ©); - void operator=(const DateTimeTestCase ©); -}; - -#endif /* _DATETIMETEST_H */ diff --git a/tests/numerics/BasicAmount.cc b/tests/numerics/BasicAmount.cc new file mode 100644 index 00000000..90cf26ec --- /dev/null +++ b/tests/numerics/BasicAmount.cc @@ -0,0 +1,625 @@ +#include "BasicAmount.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BasicAmountTestCase, "numerics"); + +void BasicAmountTestCase::setUp() { + ledger::initialize(); +} +void BasicAmountTestCase::tearDown() { + ledger::shutdown(); +} + +void BasicAmountTestCase::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(std::string("123456")); + amount_t x8(std::string("123.456")); + amount_t x9(x3); + amount_t x10(x6); + amount_t x11(x8); + + assertEqual(amount_t(0L), x0); + assertEqual(amount_t(), x0); + assertEqual(amount_t("0"), x0); + assertEqual(amount_t("0.0"), x0); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(x10, x9); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x5.valid()); + CPPUNIT_ASSERT(x6.valid()); + CPPUNIT_ASSERT(x7.valid()); + CPPUNIT_ASSERT(x8.valid()); + CPPUNIT_ASSERT(x9.valid()); + CPPUNIT_ASSERT(x10.valid()); + CPPUNIT_ASSERT(x11.valid()); +} + +void BasicAmountTestCase::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(std::string("-123456")); + amount_t x8(std::string("-123.456")); + amount_t x9(- x3); + + assertEqual(amount_t(0L), x0); + 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); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x5.valid()); + CPPUNIT_ASSERT(x6.valid()); + CPPUNIT_ASSERT(x7.valid()); + CPPUNIT_ASSERT(x8.valid()); + CPPUNIT_ASSERT(x9.valid()); + CPPUNIT_ASSERT(x10.valid()); +} + +void BasicAmountTestCase::testAssignment() +{ + 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 = amount_t(x6); + + assertEqual(amount_t(0L), x0); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(x10, x9); + + x0 = amount_t(); + x1 = 123456L; + x2 = 123456UL; + x3 = 123.456; + x5 = "123456"; + x6 = "123.456"; + x7 = std::string("123456"); + x8 = std::string("123.456"); + x9 = x3; + x10 = amount_t(x6); + + assertEqual(amount_t(0L), x0); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(x10, x9); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x5.valid()); + CPPUNIT_ASSERT(x6.valid()); + CPPUNIT_ASSERT(x7.valid()); + CPPUNIT_ASSERT(x8.valid()); + CPPUNIT_ASSERT(x9.valid()); + CPPUNIT_ASSERT(x10.valid()); +} + +void BasicAmountTestCase::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.0F); + + CPPUNIT_ASSERT(x1 == 123456L); + CPPUNIT_ASSERT(x1 != x2); + CPPUNIT_ASSERT(x1 == (x2 - x3)); + CPPUNIT_ASSERT(x1 == x4); + CPPUNIT_ASSERT(x4 == x5); + CPPUNIT_ASSERT(x4 == x6); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x4.valid()); + CPPUNIT_ASSERT(x5.valid()); + CPPUNIT_ASSERT(x6.valid()); +} + +void BasicAmountTestCase::testIntegerAddition() +{ + 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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x4.valid()); +} + +void BasicAmountTestCase::testFractionalAddition() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEqual(amount_t(579.579), x1 + y1); + assertEqual(amount_t(579.579), x1 + 456.456); + assertEqual(amount_t(579.579), 456.456 + x1); + + x1 += amount_t(456.456); + assertEqual(amount_t(579.579), x1); + x1 += 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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x2.valid()); +} + +void BasicAmountTestCase::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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x4.valid()); + CPPUNIT_ASSERT(y4.valid()); +} + +void BasicAmountTestCase::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 -= 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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(y2.valid()); +} + +void BasicAmountTestCase::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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x4.valid()); +} + +void BasicAmountTestCase::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 * 456.456); + assertEqual(amount_t("56200.232088"), amount_t(456.456) * x1); + assertEqual(amount_t("56200.232088"), 456.456 * x1); + + x1 *= amount_t(123.123); + assertEqual(amount_t("15159.273129"), x1); + x1 *= 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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x2.valid()); +} + +void BasicAmountTestCase::testIntegerDivision() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertThrow(x1 / 0L, amount_exception); + 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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x4.valid()); + CPPUNIT_ASSERT(y4.valid()); +} + +void BasicAmountTestCase::testFractionalDivision() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertThrow(x1 / 0L, amount_exception); + assertEqual(amount_t("0.008121959"), amount_t(1.0) / x1); + assertEqual(amount_t("0.008121959"), 1.0 / x1); + assertEqual(x1, x1 / 1.0); + assertEqual(amount_t("0.008121959"), amount_t(1.0) / x1); + assertEqual(amount_t("0.008121959"), 1.0 / x1); + assertEqual(- x1, x1 / -1.0); + assertEqual(- amount_t("0.008121959"), amount_t(-1.0) / x1); + assertEqual(- amount_t("0.008121959"), -1.0 / x1); + assertEqual(amount_t("0.269736842105263"), x1 / y1); + assertEqual(amount_t("3.707317073170732"), y1 / x1); + assertEqual(amount_t("0.269736842105263"), x1 / 456.456); + assertEqual(amount_t("3.707317073170732"), amount_t(456.456) / x1); + assertEqual(amount_t("3.707317073170732"), 456.456 / x1); + + x1 /= amount_t(456.456); + assertEqual(amount_t("0.269736842105263"), x1); + x1 /= 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); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(y1.valid()); + CPPUNIT_ASSERT(x4.valid()); + CPPUNIT_ASSERT(y4.valid()); +} + +void BasicAmountTestCase::testIntegerConversion() +{ + amount_t x1(123456L); + + assertEqual(true, bool(x1)); + assertEqual(123456L, long(x1)); + assertEqual(123456.0, double(x1)); + assertEqual(string("123456"), x1.to_string()); + assertEqual(string("123456"), x1.quantity_string()); + + CPPUNIT_ASSERT(x1.valid()); +} + +void BasicAmountTestCase::testFractionalConversion() +{ + amount_t x1(1234.56); + + assertEqual(true, bool(x1)); + assertEqual(1234L, long(x1)); + assertEqual(1234.56, double(x1)); + assertEqual(string("1234.56"), x1.to_string()); + assertEqual(string("1234.56"), x1.quantity_string()); + + CPPUNIT_ASSERT(x1.valid()); +} + +void BasicAmountTestCase::testFractionalRound() +{ + amount_t x1("1234.567890"); + + assertEqual(amount_t("1234.56789"), x1.round(6)); + assertEqual(amount_t("1234.56789"), x1.round(5)); + assertEqual(amount_t("1234.5679"), x1.round(4)); + assertEqual(amount_t("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("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(), x5.round(36)); + + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x4.valid()); +} + +void BasicAmountTestCase::testTruth() +{ + amount_t x0; + amount_t x1("1234"); + amount_t x2("1234.56"); + + if (x0) + CPPUNIT_ASSERT(false); + else + CPPUNIT_ASSERT(true); + + if (x1) + CPPUNIT_ASSERT(true); + else + CPPUNIT_ASSERT(false); + + if (x2) + CPPUNIT_ASSERT(true); + else + CPPUNIT_ASSERT(false); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); +} + +void BasicAmountTestCase::testForZero() +{ + amount_t x0; + amount_t x1("0.000000000000000000001"); + + CPPUNIT_ASSERT(! x0); + CPPUNIT_ASSERT(x1); + CPPUNIT_ASSERT(x0.zero()); + CPPUNIT_ASSERT(x0.realzero()); + CPPUNIT_ASSERT(! x1.zero()); + CPPUNIT_ASSERT(! x1.realzero()); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); +} + +void BasicAmountTestCase::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"); + + CPPUNIT_ASSERT(x0 > x1); + CPPUNIT_ASSERT(x0 < x2); + CPPUNIT_ASSERT(x0 > x3); + CPPUNIT_ASSERT(x0 < x4); + CPPUNIT_ASSERT(x0 > x5); + CPPUNIT_ASSERT(x0 < x6); + + CPPUNIT_ASSERT(x1 > x3); + CPPUNIT_ASSERT(x3 <= x5); + CPPUNIT_ASSERT(x3 >= x5); + CPPUNIT_ASSERT(x3 < x1); + CPPUNIT_ASSERT(x3 < x4); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x4.valid()); + CPPUNIT_ASSERT(x5.valid()); + CPPUNIT_ASSERT(x6.valid()); +} + +void BasicAmountTestCase::testSign() +{ + amount_t x0; + amount_t x1("0.0000000000000000000000000000000000001"); + amount_t x2("-0.0000000000000000000000000000000000001"); + amount_t x3("1"); + amount_t x4("-1"); + + CPPUNIT_ASSERT(! x0.sign()); + CPPUNIT_ASSERT(x1.sign() > 0); + CPPUNIT_ASSERT(x2.sign() < 0); + CPPUNIT_ASSERT(x3.sign() > 0); + CPPUNIT_ASSERT(x4.sign() < 0); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); + CPPUNIT_ASSERT(x3.valid()); + CPPUNIT_ASSERT(x4.valid()); +} + +void BasicAmountTestCase::testAbs() +{ + amount_t x0; + amount_t x1(-1234L); + amount_t x2(1234L); + + assertEqual(amount_t(), x0.abs()); + assertEqual(amount_t(1234L), x1.abs()); + assertEqual(amount_t(1234L), x2.abs()); + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); + CPPUNIT_ASSERT(x2.valid()); +} + +void BasicAmountTestCase::testPrinting() +{ + amount_t x0; + amount_t x1("982340823.380238098235098235098235098"); + + { + std::ostringstream bufstr; + bufstr << x0; + + assertEqual(std::string("0"), bufstr.str()); + } + + { + std::ostringstream bufstr; + bufstr << x1; + + assertEqual(std::string("982340823.380238098235098235098235098"), + bufstr.str()); + } + + CPPUNIT_ASSERT(x0.valid()); + CPPUNIT_ASSERT(x1.valid()); +} diff --git a/tests/numerics/BasicAmount.h b/tests/numerics/BasicAmount.h new file mode 100644 index 00000000..2c107f45 --- /dev/null +++ b/tests/numerics/BasicAmount.h @@ -0,0 +1,68 @@ +#ifndef _BASICAMOUNT_H +#define _BASICAMOUNT_H + +#include "UnitTests.h" + +class BasicAmountTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(BasicAmountTestCase); + + CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testNegation); + CPPUNIT_TEST(testAssignment); + CPPUNIT_TEST(testEquality); + CPPUNIT_TEST(testIntegerAddition); + CPPUNIT_TEST(testFractionalAddition); + CPPUNIT_TEST(testIntegerSubtraction); + CPPUNIT_TEST(testFractionalSubtraction); + CPPUNIT_TEST(testIntegerMultiplication); + CPPUNIT_TEST(testFractionalMultiplication); + CPPUNIT_TEST(testIntegerDivision); + CPPUNIT_TEST(testFractionalDivision); + CPPUNIT_TEST(testIntegerConversion); + CPPUNIT_TEST(testFractionalConversion); + CPPUNIT_TEST(testFractionalRound); + CPPUNIT_TEST(testTruth); + CPPUNIT_TEST(testForZero); + CPPUNIT_TEST(testComparisons); + CPPUNIT_TEST(testSign); + CPPUNIT_TEST(testAbs); + CPPUNIT_TEST(testPrinting); + + CPPUNIT_TEST_SUITE_END(); + +public: + BasicAmountTestCase() {} + virtual ~BasicAmountTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + void testNegation(); + void testAssignment(); + void testEquality(); + void testIntegerAddition(); + void testFractionalAddition(); + void testIntegerSubtraction(); + void testFractionalSubtraction(); + void testIntegerMultiplication(); + void testFractionalMultiplication(); + void testIntegerDivision(); + void testFractionalDivision(); + void testIntegerConversion(); + void testFractionalConversion(); + void testFractionalRound(); + void testTruth(); + void testForZero(); + void testComparisons(); + void testSign(); + void testAbs(); + void testPrinting(); + +private: + BasicAmountTestCase(const BasicAmountTestCase ©); + void operator=(const BasicAmountTestCase ©); +}; + +#endif /* _BASICAMOUNT_H */ diff --git a/tests/numerics/Commodity.cc b/tests/numerics/Commodity.cc new file mode 100644 index 00000000..e7e2a18c --- /dev/null +++ b/tests/numerics/Commodity.cc @@ -0,0 +1,55 @@ +#include "Commodity.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommodityTestCase, "numerics"); + +void CommodityTestCase::setUp() { + ledger::initialize(); +} +void CommodityTestCase::tearDown() { + ledger::shutdown(); +} + +void CommodityTestCase::testPriceHistory() +{ + ptime jan17_07 = parse_datetime("2007/01/17 00:00:00"); + ptime feb27_07 = parse_datetime("2007/02/27 18:00:00"); + ptime feb28_07 = parse_datetime("2007/02/28 06:00:00"); + ptime feb28_07sbm = parse_datetime("2007/02/28 11:59:59"); + ptime mar01_07 = parse_datetime("2007/03/01 00:00:00"); + ptime apr15_07 = parse_datetime("2007/04/15 13:00:00"); + + // jww (2007-04-17): tbd + amount_t x1("100.10 AAPL"); + + // 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")); + + assertEqual(amount_t("$1831.83"), x1.value(feb28_07sbm)); + assertEqual(amount_t("$2124.12"), x1.value(now)); + + 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/tests/numerics/Commodity.h b/tests/numerics/Commodity.h new file mode 100644 index 00000000..dafa03e9 --- /dev/null +++ b/tests/numerics/Commodity.h @@ -0,0 +1,34 @@ +#ifndef _COMMMODITY_H +#define _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: + CommodityTestCase() {} + virtual ~CommodityTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testPriceHistory(); + void testLots(); + void testScalingBase(); + void testReduction(); + +private: + CommodityTestCase(const CommodityTestCase ©); + void operator=(const CommodityTestCase ©); +}; + +#endif /* _COMMMODITY_H */ diff --git a/tests/numerics/CommodityAmount.cc b/tests/numerics/CommodityAmount.cc new file mode 100644 index 00000000..7ed7f403 --- /dev/null +++ b/tests/numerics/CommodityAmount.cc @@ -0,0 +1,696 @@ +#include "CommodityAmount.h" + +#define internalAmount(x) amount_t::exact(x) + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommodityAmountTestCase, "numerics"); + +void CommodityAmountTestCase::setUp() +{ + ledger::initialize(); + + // Cause the display precision for dollars to be initialized to 2. + amount_t x1("$1.00"); + assertTrue(x1); + amount_t::full_strings = true; // makes error reports from UnitTests accurate +} + +void CommodityAmountTestCase::tearDown() +{ + amount_t::full_strings = false; + ledger::shutdown(); +} + +void CommodityAmountTestCase::testConstructors() +{ + 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 CommodityAmountTestCase::testNegation() +{ + 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 CommodityAmountTestCase::testAssignment() +{ + 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 CommodityAmountTestCase::testEquality() +{ + amount_t x0; + 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€"; + + assertTrue(x0.null()); + assertTrue(x0.zero()); + assertTrue(x0.realzero()); + assertTrue(x0.sign() == 0); + assertTrue(x0.compare(x1) < 0); + assertTrue(x0.compare(x2) > 0); + assertTrue(x0.compare(x0) == 0); + + 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 CommodityAmountTestCase::testAddition() +{ + 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_exception); + assertThrow(x1 + x3, amount_exception); + assertThrow(x1 + x4, amount_exception); + assertThrow(x1 + x5, amount_exception); + assertThrow(x1 + x6, amount_exception); + assertThrow(x1 + 123.45, amount_exception); + assertThrow(x1 + 123L, amount_exception); + + 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 CommodityAmountTestCase::testSubtraction() +{ + 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_exception); + assertThrow(x1 - x3, amount_exception); + assertThrow(x1 - x4, amount_exception); + assertThrow(x1 - x5, amount_exception); + assertThrow(x1 - x6, amount_exception); + assertThrow(x1 - 123.45, amount_exception); + assertThrow(x1 - 123L, amount_exception); + + 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 CommodityAmountTestCase::testMultiplication() +{ + 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 * x3, amount_exception); + assertThrow(x1 * x4, amount_exception); + assertThrow(x1 * x5, amount_exception); + + x1 *= amount_t("123.12"); + assertEqual(internalAmount("$15158.5344"), x1); + assertEqual(string("$15158.53"), x1.to_string()); + x1 *= 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 CommodityAmountTestCase::testDivision() +{ + 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_exception); + 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 / x3, amount_exception); + assertThrow(x1 / x4, amount_exception); + assertThrow(x1 / x5, amount_exception); + + x1 /= amount_t("123.12"); + assertEqual(internalAmount("$1.00"), x1); + assertEqual(string("$1.00"), x1.to_string()); + x1 /= 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 CommodityAmountTestCase::testConversion() +{ + amount_t x1("$1234.56"); + + assertEqual(true, bool(x1)); + assertEqual(1234L, long(x1)); + assertEqual(1234.56, double(x1)); + assertEqual(string("$1234.56"), x1.to_string()); + assertEqual(string("1234.56"), x1.quantity_string()); + + assertValid(x1); +} + +void CommodityAmountTestCase::testRound() +{ + 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 *= 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 CommodityAmountTestCase::testDisplayRound() +{ + amount_t x1("$0.85"); + amount_t x2("$0.1"); + + x1 *= 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 CommodityAmountTestCase::testTruth() +{ + amount_t x1("$1234"); + amount_t x2("$1234.56"); + + if (x1) + CPPUNIT_ASSERT(true); + else + CPPUNIT_ASSERT(false); + + if (x2) + CPPUNIT_ASSERT(true); + else + CPPUNIT_ASSERT(false); + + assertValid(x1); + assertValid(x2); +} + +void CommodityAmountTestCase::testForZero() +{ + amount_t x1(internalAmount("$0.000000000000000000001")); + + assertFalse(x1); + assertTrue(x1.zero()); + assertFalse(x1.realzero()); + + assertValid(x1); +} + +void CommodityAmountTestCase::testComparisons() +{ + amount_t x0; + 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"); + + assertTrue(x0 > x1); + assertTrue(x0 < x2); + assertTrue(x0 > x3); + assertTrue(x0 < x4); + assertTrue(x0 > x5); + assertTrue(x0 < x6); + + assertTrue(x1 > x3); + assertTrue(x3 <= x5); + assertTrue(x3 < x5); + assertTrue(x3 <= x5); + assertFalse(x3 == x5); + assertTrue(x3 < x1); + assertTrue(x3 < x4); + + assertValid(x0); + assertValid(x1); + assertValid(x2); + assertValid(x3); + assertValid(x4); + assertValid(x5); + assertValid(x6); +} + +void CommodityAmountTestCase::testSign() +{ + amount_t x0; + amount_t x1(internalAmount("$0.0000000000000000000000000000000000001")); + amount_t x2(internalAmount("$-0.0000000000000000000000000000000000001")); + amount_t x3("$1"); + amount_t x4("$-1"); + + assertFalse(x0.sign()); + 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 CommodityAmountTestCase::testAbs() +{ + amount_t x0; + amount_t x1("$-1234.56"); + amount_t x2("$1234.56"); + + assertEqual(amount_t(), x0.abs()); + assertEqual(amount_t("$1234.56"), x1.abs()); + assertEqual(amount_t("$1234.56"), x2.abs()); + + assertValid(x0); + assertValid(x1); + assertValid(x2); +} + +void CommodityAmountTestCase::testPrinting() +{ + amount_t x0; + amount_t x1(internalAmount("$982340823.386238098235098235098235098")); + amount_t x2("$982340823.38"); + + { + std::ostringstream bufstr; + bufstr << x0; + + assertEqual(std::string("0"), bufstr.str()); + } + + { + 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(x0); + assertValid(x1); + assertValid(x2); +} + diff --git a/tests/numerics/CommodityAmount.h b/tests/numerics/CommodityAmount.h new file mode 100644 index 00000000..5ffa7810 --- /dev/null +++ b/tests/numerics/CommodityAmount.h @@ -0,0 +1,60 @@ +#ifndef _COMMODITYAMOUNT_H +#define _COMMODITYAMOUNT_H + +#include "UnitTests.h" + +class CommodityAmountTestCase : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(CommodityAmountTestCase); + + CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testNegation); + CPPUNIT_TEST(testAssignment); + CPPUNIT_TEST(testEquality); + CPPUNIT_TEST(testAddition); + CPPUNIT_TEST(testSubtraction); + CPPUNIT_TEST(testMultiplication); + CPPUNIT_TEST(testDivision); + CPPUNIT_TEST(testConversion); + CPPUNIT_TEST(testRound); + CPPUNIT_TEST(testDisplayRound); + CPPUNIT_TEST(testTruth); + CPPUNIT_TEST(testForZero); + CPPUNIT_TEST(testComparisons); + CPPUNIT_TEST(testSign); + CPPUNIT_TEST(testAbs); + CPPUNIT_TEST(testPrinting); + + CPPUNIT_TEST_SUITE_END(); + +public: + CommodityAmountTestCase() {} + virtual ~CommodityAmountTestCase() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + void testNegation(); + void testAssignment(); + void testEquality(); + void testAddition(); + void testSubtraction(); + void testMultiplication(); + void testDivision(); + void testConversion(); + void testRound(); + void testDisplayRound(); + void testTruth(); + void testForZero(); + void testComparisons(); + void testSign(); + void testAbs(); + void testPrinting(); + +private: + CommodityAmountTestCase(const CommodityAmountTestCase ©); + void operator=(const CommodityAmountTestCase ©); +}; + +#endif /* _COMMODITYAMOUNT_H */ diff --git a/tests/numerics/DateTime.cc b/tests/numerics/DateTime.cc new file mode 100644 index 00000000..24b8dd16 --- /dev/null +++ b/tests/numerics/DateTime.cc @@ -0,0 +1,79 @@ +#include "DateTimeTest.h" + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DateTimeTestCase, "numerics"); + +void DateTimeTestCase::setUp() {} +void DateTimeTestCase::tearDown() {} + +void DateTimeTestCase::testConstructors() +{ + 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 +} diff --git a/tests/numerics/DateTimeTest.h b/tests/numerics/DateTimeTest.h new file mode 100644 index 00000000..abc914cd --- /dev/null +++ b/tests/numerics/DateTimeTest.h @@ -0,0 +1,28 @@ +#ifndef _DATETIMETEST_H +#define _DATETIMETEST_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 ©); + void operator=(const DateTimeTestCase ©); +}; + +#endif /* _DATETIMETEST_H */ diff --git a/tests/python/PyUnitTests.py b/tests/python/PyUnitTests.py new file mode 100755 index 00000000..71267798 --- /dev/null +++ b/tests/python/PyUnitTests.py @@ -0,0 +1,4 @@ +#!/bin/sh + +PYTHONPATH="%builddir%":"%srcdir%":$PYTHONPATH \ + python "%srcdir%"/tests/python/UnitTests.py diff --git a/tests/python/UnitTests.py b/tests/python/UnitTests.py index 7a10c9e6..84b8432b 100644 --- a/tests/python/UnitTests.py +++ b/tests/python/UnitTests.py @@ -1,7 +1,7 @@ from unittest import TextTestRunner, TestSuite -import tests.python.corelib.numerics.BasicAmount as BasicAmount -import tests.python.corelib.numerics.CommodityAmount as CommodityAmount +import tests.python.numerics.BasicAmount as BasicAmount +import tests.python.numerics.CommodityAmount as CommodityAmount suites = [ BasicAmount.suite(), diff --git a/tests/python/corelib/.gitignore b/tests/python/corelib/.gitignore deleted file mode 100644 index 0d20b648..00000000 --- a/tests/python/corelib/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/tests/python/corelib/__init__.py b/tests/python/corelib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/python/corelib/balances/__init__.py b/tests/python/corelib/balances/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/python/corelib/numerics/.gitignore b/tests/python/corelib/numerics/.gitignore deleted file mode 100644 index 0d20b648..00000000 --- a/tests/python/corelib/numerics/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/tests/python/corelib/numerics/BasicAmount.py b/tests/python/corelib/numerics/BasicAmount.py deleted file mode 100644 index bdc7dbe9..00000000 --- a/tests/python/corelib/numerics/BasicAmount.py +++ /dev/null @@ -1,527 +0,0 @@ -import sys -sys.path.append("/home/johnw/src/ledger") -sys.path.append("/home/johnw/Products/ledger") - -import unittest -import exceptions - -from ledger import amount - -class BasicAmountTestCase(unittest.TestCase): - def testConstructors(self): - x0 = amount() - x1 = amount(123456) - x2 = amount(123456L) - x3 = amount(123.456) - x5 = amount("123456") - x6 = amount("123.456") - x9 = amount(x3) - x10 = amount(x6) - - self.assertEqual(amount(0), x0) - self.assertEqual(x2, x1) - self.assertEqual(x5, x1) - self.assertEqual(x6, x3) - self.assertEqual(x10, x3) - self.assertEqual(x10, x9) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x5.valid()) - self.assertTrue(x6.valid()) - self.assertTrue(x9.valid()) - self.assertTrue(x10.valid()) - - def testNegation(self): - x0 = amount() - x1 = amount(-123456) - x3 = amount(-123.456) - x5 = amount("-123456") - x6 = amount("-123.456") - x9 = amount(- x3) - - self.assertEqual(amount(0), x0) - self.assertEqual(x5, x1) - self.assertEqual(x6, x3) - self.assertEqual(- x6, x9) - self.assertEqual(x3.negate(), x9) - - x10 = amount(x9.negate()) - - self.assertEqual(x3, x10) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x5.valid()) - self.assertTrue(x6.valid()) - self.assertTrue(x9.valid()) - self.assertTrue(x10.valid()) - - def testAssignment(self): - x0 = amount() - x1 = amount(123456) - x2 = amount(123456L) - x3 = amount(123.456) - x5 = amount("123456") - x6 = amount("123.456") - x9 = x3 - x10 = amount(x6) - - self.assertEqual(amount(0), x0) - self.assertEqual(x2, x1) - self.assertEqual(x5, x1) - self.assertEqual(x6, x3) - self.assertEqual(x10, x3) - self.assertEqual(x10, x9) - - x0 = amount() - x1 = amount(123456) - x2 = amount(123456L) - x3 = amount(123.456) - x5 = amount("123456") - x6 = amount("123.456") - x9 = x3 - x10 = amount(x6) - - self.assertEqual(amount(0), x0) - self.assertEqual(x2, x1) - self.assertEqual(x5, x1) - self.assertEqual(x6, x3) - self.assertEqual(x10, x3) - self.assertEqual(x10, x9) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x5.valid()) - self.assertTrue(x6.valid()) - self.assertTrue(x9.valid()) - self.assertTrue(x10.valid()) - - def testEquality(self): - x1 = amount(123456) - x2 = amount(456789) - x3 = amount(333333) - x4 = amount(123456.0) - x5 = 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(x1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x4.valid()) - self.assertTrue(x5.valid()) - - def testIntegerAddition(self): - 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x4.valid()) - - def testFractionalAddition(self): - x1 = amount(123.123) - y1 = amount(456.456) - - self.assertEqual(amount(579.579), x1 + y1) - self.assertEqual(amount(579.579), x1 + 456.456) - self.assertEqual(amount(579.579), 456.456 + x1) - - x1 += amount(456.456) - self.assertEqual(amount(579.579), x1) - x1 += 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x2.valid()) - - 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x4.valid()) - self.assertTrue(y4.valid()) - - 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 -= 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(y2.valid()) - - 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x4.valid()) - - 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 * 456.456) - self.assertEqual(amount("56200.232088"), amount(456.456) * x1) - self.assertEqual(amount("56200.232088"), 456.456 * x1) - - x1 *= amount(123.123) - self.assertEqual(amount("15159.273129"), x1) - x1 *= 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x2.valid()) - - def divideByZero(self, amt): - return amt / 0 - - def testIntegerDivision(self): - x1 = amount(123) - y1 = amount(456) - - self.assertRaises(exceptions.ArithmeticError, self.divideByZero, x1) - 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x4.valid()) - self.assertTrue(y4.valid()) - - def testFractionalDivision(self): - x1 = amount(123.123) - y1 = amount(456.456) - - self.assertRaises(exceptions.ArithmeticError, self.divideByZero, x1) - self.assertEqual(amount("0.008121959"), amount(1.0) / x1) - self.assertEqual(amount("0.008121959"), 1.0 / x1) - self.assertEqual(x1, x1 / 1.0) - self.assertEqual(amount("0.008121959"), amount(1.0) / x1) - self.assertEqual(amount("0.008121959"), 1.0 / x1) - self.assertEqual(- x1, x1 / -1.0) - self.assertEqual(- amount("0.008121959"), amount(-1.0) / x1) - self.assertEqual(- amount("0.008121959"), -1.0 / x1) - self.assertEqual(amount("0.269736842105263"), x1 / y1) - self.assertEqual(amount("3.707317073170732"), y1 / x1) - self.assertEqual(amount("0.269736842105263"), x1 / 456.456) - self.assertEqual(amount("3.707317073170732"), amount(456.456) / x1) - self.assertEqual(amount("3.707317073170732"), 456.456 / x1) - - x1 /= amount(456.456) - self.assertEqual(amount("0.269736842105263"), x1) - x1 /= 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.assertTrue(x1.valid()) - self.assertTrue(y1.valid()) - self.assertTrue(x4.valid()) - self.assertTrue(y4.valid()) - - def testIntegerConversion(self): - x1 = amount(123456) - - self.assertEqual(True, bool(x1)) - self.assertEqual(123456, int(x1)) - self.assertEqual(123456.0, float(x1)) - self.assertEqual("123456", x1.to_string()) - self.assertEqual("123456", x1.quantity_string()) - - self.assertTrue(x1.valid()) - - def testFractionalConversion(self): - x1 = amount(1234.56) - - self.assertEqual(True, not (not x1)) - self.assertEqual(1234, int(x1)) - self.assertEqual(1234.56, float(x1)) - self.assertEqual("1234.56", x1.to_string()) - self.assertEqual("1234.56", x1.quantity_string()) - - self.assertTrue(x1.valid()) - - def testFractionalRound(self): - x1 = amount("1234.567890") - - self.assertEqual(amount("1234.56789"), x1.round(6)) - self.assertEqual(amount("1234.56789"), x1.round(5)) - self.assertEqual(amount("1234.5679"), x1.round(4)) - self.assertEqual(amount("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("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)) - - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x4.valid()) - - def testTruth(self): - x0 = amount() - x1 = amount("1234") - x2 = amount("1234.56") - - self.assertTrue(not x0) - self.assertTrue(x1 ) - self.assertTrue(x2) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - - def testForZero(self): - x0 = amount() - x1 = amount("0.000000000000000000001") - - self.assertTrue(not x0) - self.assertTrue(x1) - self.assertTrue(x0.zero()) - self.assertTrue(x0.realzero()) - self.assertTrue(not x1.zero()) - self.assertTrue(not x1.realzero()) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - - 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.assertTrue(x0 > x1) - self.assertTrue(x0 < x2) - self.assertTrue(x0 > x3) - self.assertTrue(x0 < x4) - self.assertTrue(x0 > x5) - self.assertTrue(x0 < x6) - - self.assertTrue(x1 > x3) - self.assertTrue(x3 <= x5) - self.assertTrue(x3 >= x5) - self.assertTrue(x3 < x1) - self.assertTrue(x3 < x4) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x4.valid()) - self.assertTrue(x5.valid()) - self.assertTrue(x6.valid()) - - def testSign(self): - x0 = amount() - x1 = amount("0.0000000000000000000000000000000000001") - x2 = amount("-0.0000000000000000000000000000000000001") - x3 = amount("1") - x4 = amount("-1") - - self.assertTrue(not x0.sign()) - self.assertTrue(x1.sign() > 0) - self.assertTrue(x2.sign() < 0) - self.assertTrue(x3.sign() > 0) - self.assertTrue(x4.sign() < 0) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - self.assertTrue(x3.valid()) - self.assertTrue(x4.valid()) - - def testAbs(self): - x0 = amount() - x1 = amount(-1234) - x2 = amount(1234) - - self.assertEqual(amount(), abs(x0)) - self.assertEqual(amount(1234), abs(x1)) - self.assertEqual(amount(1234), abs(x2)) - - self.assertTrue(x0.valid()) - self.assertTrue(x1.valid()) - self.assertTrue(x2.valid()) - - def testPrinting(self): - pass - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(BasicAmountTestCase) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/python/corelib/numerics/CommodityAmount.py b/tests/python/corelib/numerics/CommodityAmount.py deleted file mode 100644 index 80f58b21..00000000 --- a/tests/python/corelib/numerics/CommodityAmount.py +++ /dev/null @@ -1,623 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest -import exceptions -import operator - -from ledger import amount - -internalAmount = amount.exact - -class CommodityAmountTestCase(unittest.TestCase): - def setUp(self): - # Cause the display precision for dollars to be initialized to 2. - x1 = amount("$1.00") - self.assertTrue(x1) - amount.full_strings = True # makes error reports from UnitTests accurate - - def tearDown(self): - amount.full_strings = False - - def assertValid(self, amt): - self.assertTrue(amt.valid()) - - def testConstructors(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 testNegation(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 testAssignment(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): - 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.null()) - self.assertTrue(x0.zero()) - self.assertTrue(x0.realzero()) - self.assertTrue(x0.sign() == 0) - self.assertTrue(x0.compare(x1) < 0) - self.assertTrue(x0.compare(x2) > 0) - self.assertTrue(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 testAddition(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, operator.add, x1, x0) - self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x3) - self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x4) - self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x5) - self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x6) - self.assertRaises(exceptions.ArithmeticError, operator.add, x1, 123.45) - self.assertRaises(exceptions.ArithmeticError, operator.add, 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 testSubtraction(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, operator.sub, x1, x0) - self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x3) - self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x4) - self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x5) - self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x6) - self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, 123.45) - self.assertRaises(exceptions.ArithmeticError, operator.sub, 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 testMultiplication(self): - 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, operator.mul, x1, x3) - self.assertRaises(exceptions.ArithmeticError, operator.mul, x1, x4) - self.assertRaises(exceptions.ArithmeticError, operator.mul, x1, x5) - - x1 *= amount("123.12") - self.assertEqual(internalAmount("$15158.5344"), x1) - self.assertEqual("$15158.53", x1.to_string()) - x1 *= 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 testDivision(self): - 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, operator.div, 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, operator.div, x1, x3) - self.assertRaises(exceptions.ArithmeticError, operator.div, x1, x4) - self.assertRaises(exceptions.ArithmeticError, operator.div, x1, x5) - - x1 /= amount("123.12") - self.assertEqual(internalAmount("$1.00"), x1) - self.assertEqual("$1.00", x1.to_string()) - x1 /= 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 testConversion(self): - x1 = amount("$1234.56") - - self.assertEqual(True, bool(x1)) - self.assertEqual(1234, int(x1)) - self.assertEqual(1234.56, float(x1)) - self.assertEqual("$1234.56", x1.to_string()) - self.assertEqual("1234.56", x1.quantity_string()) - - self.assertValid(x1) - - def testRound(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)) - - self.assertValid(x1) - self.assertValid(x2) - self.assertValid(x3) - self.assertValid(x4) - - def testDisplayRound(self): - x1 = amount("$0.85") - x2 = amount("$0.1") - - x1 *= 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 testTruth(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): - x1 = amount(internalAmount("$0.000000000000000000001")) - - self.assertFalse(x1) - self.assertTrue(x1.zero()) - self.assertFalse(x1.realzero()) - - self.assertValid(x1) - - def testComparisons(self): - x0 = amount() - 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") - - self.assertTrue(x0 > x1) - self.assertTrue(x0 < x2) - self.assertTrue(x0 > x3) - self.assertTrue(x0 < x4) - self.assertTrue(x0 > x5) - self.assertTrue(x0 < x6) - - 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.assertValid(x0) - self.assertValid(x1) - self.assertValid(x2) - self.assertValid(x3) - self.assertValid(x4) - self.assertValid(x5) - self.assertValid(x6) - - def testSign(self): - x0 = amount() - x1 = amount(internalAmount("$0.0000000000000000000000000000000000001")) - x2 = amount(internalAmount("$-0.0000000000000000000000000000000000001")) - x3 = amount("$1") - x4 = amount("$-1") - - self.assertFalse(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 testAbs(self): - x0 = amount() - x1 = amount("$-1234.56") - x2 = amount("$1234.56") - - self.assertEqual(amount(), abs(x0)) - self.assertEqual(amount("$1234.56"), abs(x1)) - self.assertEqual(amount("$1234.56"), abs(x2)) - - self.assertValid(x0) - self.assertValid(x1) - self.assertValid(x2) - - def testPrinting(self): - pass - - -def suite(): - return unittest.TestLoader().loadTestsFromTestCase(CommodityAmountTestCase) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/python/corelib/numerics/__init__.py b/tests/python/corelib/numerics/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/python/corelib/values/__init__.py b/tests/python/corelib/values/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/python/numerics/.gitignore b/tests/python/numerics/.gitignore new file mode 100644 index 00000000..0d20b648 --- /dev/null +++ b/tests/python/numerics/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/tests/python/numerics/BasicAmount.py b/tests/python/numerics/BasicAmount.py new file mode 100644 index 00000000..bdc7dbe9 --- /dev/null +++ b/tests/python/numerics/BasicAmount.py @@ -0,0 +1,527 @@ +import sys +sys.path.append("/home/johnw/src/ledger") +sys.path.append("/home/johnw/Products/ledger") + +import unittest +import exceptions + +from ledger import amount + +class BasicAmountTestCase(unittest.TestCase): + def testConstructors(self): + x0 = amount() + x1 = amount(123456) + x2 = amount(123456L) + x3 = amount(123.456) + x5 = amount("123456") + x6 = amount("123.456") + x9 = amount(x3) + x10 = amount(x6) + + self.assertEqual(amount(0), x0) + self.assertEqual(x2, x1) + self.assertEqual(x5, x1) + self.assertEqual(x6, x3) + self.assertEqual(x10, x3) + self.assertEqual(x10, x9) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x5.valid()) + self.assertTrue(x6.valid()) + self.assertTrue(x9.valid()) + self.assertTrue(x10.valid()) + + def testNegation(self): + x0 = amount() + x1 = amount(-123456) + x3 = amount(-123.456) + x5 = amount("-123456") + x6 = amount("-123.456") + x9 = amount(- x3) + + self.assertEqual(amount(0), x0) + self.assertEqual(x5, x1) + self.assertEqual(x6, x3) + self.assertEqual(- x6, x9) + self.assertEqual(x3.negate(), x9) + + x10 = amount(x9.negate()) + + self.assertEqual(x3, x10) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x5.valid()) + self.assertTrue(x6.valid()) + self.assertTrue(x9.valid()) + self.assertTrue(x10.valid()) + + def testAssignment(self): + x0 = amount() + x1 = amount(123456) + x2 = amount(123456L) + x3 = amount(123.456) + x5 = amount("123456") + x6 = amount("123.456") + x9 = x3 + x10 = amount(x6) + + self.assertEqual(amount(0), x0) + self.assertEqual(x2, x1) + self.assertEqual(x5, x1) + self.assertEqual(x6, x3) + self.assertEqual(x10, x3) + self.assertEqual(x10, x9) + + x0 = amount() + x1 = amount(123456) + x2 = amount(123456L) + x3 = amount(123.456) + x5 = amount("123456") + x6 = amount("123.456") + x9 = x3 + x10 = amount(x6) + + self.assertEqual(amount(0), x0) + self.assertEqual(x2, x1) + self.assertEqual(x5, x1) + self.assertEqual(x6, x3) + self.assertEqual(x10, x3) + self.assertEqual(x10, x9) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x5.valid()) + self.assertTrue(x6.valid()) + self.assertTrue(x9.valid()) + self.assertTrue(x10.valid()) + + def testEquality(self): + x1 = amount(123456) + x2 = amount(456789) + x3 = amount(333333) + x4 = amount(123456.0) + x5 = 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(x1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x4.valid()) + self.assertTrue(x5.valid()) + + def testIntegerAddition(self): + 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x4.valid()) + + def testFractionalAddition(self): + x1 = amount(123.123) + y1 = amount(456.456) + + self.assertEqual(amount(579.579), x1 + y1) + self.assertEqual(amount(579.579), x1 + 456.456) + self.assertEqual(amount(579.579), 456.456 + x1) + + x1 += amount(456.456) + self.assertEqual(amount(579.579), x1) + x1 += 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x2.valid()) + + 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x4.valid()) + self.assertTrue(y4.valid()) + + 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 -= 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(y2.valid()) + + 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x4.valid()) + + 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 * 456.456) + self.assertEqual(amount("56200.232088"), amount(456.456) * x1) + self.assertEqual(amount("56200.232088"), 456.456 * x1) + + x1 *= amount(123.123) + self.assertEqual(amount("15159.273129"), x1) + x1 *= 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x2.valid()) + + def divideByZero(self, amt): + return amt / 0 + + def testIntegerDivision(self): + x1 = amount(123) + y1 = amount(456) + + self.assertRaises(exceptions.ArithmeticError, self.divideByZero, x1) + 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x4.valid()) + self.assertTrue(y4.valid()) + + def testFractionalDivision(self): + x1 = amount(123.123) + y1 = amount(456.456) + + self.assertRaises(exceptions.ArithmeticError, self.divideByZero, x1) + self.assertEqual(amount("0.008121959"), amount(1.0) / x1) + self.assertEqual(amount("0.008121959"), 1.0 / x1) + self.assertEqual(x1, x1 / 1.0) + self.assertEqual(amount("0.008121959"), amount(1.0) / x1) + self.assertEqual(amount("0.008121959"), 1.0 / x1) + self.assertEqual(- x1, x1 / -1.0) + self.assertEqual(- amount("0.008121959"), amount(-1.0) / x1) + self.assertEqual(- amount("0.008121959"), -1.0 / x1) + self.assertEqual(amount("0.269736842105263"), x1 / y1) + self.assertEqual(amount("3.707317073170732"), y1 / x1) + self.assertEqual(amount("0.269736842105263"), x1 / 456.456) + self.assertEqual(amount("3.707317073170732"), amount(456.456) / x1) + self.assertEqual(amount("3.707317073170732"), 456.456 / x1) + + x1 /= amount(456.456) + self.assertEqual(amount("0.269736842105263"), x1) + x1 /= 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.assertTrue(x1.valid()) + self.assertTrue(y1.valid()) + self.assertTrue(x4.valid()) + self.assertTrue(y4.valid()) + + def testIntegerConversion(self): + x1 = amount(123456) + + self.assertEqual(True, bool(x1)) + self.assertEqual(123456, int(x1)) + self.assertEqual(123456.0, float(x1)) + self.assertEqual("123456", x1.to_string()) + self.assertEqual("123456", x1.quantity_string()) + + self.assertTrue(x1.valid()) + + def testFractionalConversion(self): + x1 = amount(1234.56) + + self.assertEqual(True, not (not x1)) + self.assertEqual(1234, int(x1)) + self.assertEqual(1234.56, float(x1)) + self.assertEqual("1234.56", x1.to_string()) + self.assertEqual("1234.56", x1.quantity_string()) + + self.assertTrue(x1.valid()) + + def testFractionalRound(self): + x1 = amount("1234.567890") + + self.assertEqual(amount("1234.56789"), x1.round(6)) + self.assertEqual(amount("1234.56789"), x1.round(5)) + self.assertEqual(amount("1234.5679"), x1.round(4)) + self.assertEqual(amount("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("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)) + + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x4.valid()) + + def testTruth(self): + x0 = amount() + x1 = amount("1234") + x2 = amount("1234.56") + + self.assertTrue(not x0) + self.assertTrue(x1 ) + self.assertTrue(x2) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + + def testForZero(self): + x0 = amount() + x1 = amount("0.000000000000000000001") + + self.assertTrue(not x0) + self.assertTrue(x1) + self.assertTrue(x0.zero()) + self.assertTrue(x0.realzero()) + self.assertTrue(not x1.zero()) + self.assertTrue(not x1.realzero()) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + + 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.assertTrue(x0 > x1) + self.assertTrue(x0 < x2) + self.assertTrue(x0 > x3) + self.assertTrue(x0 < x4) + self.assertTrue(x0 > x5) + self.assertTrue(x0 < x6) + + self.assertTrue(x1 > x3) + self.assertTrue(x3 <= x5) + self.assertTrue(x3 >= x5) + self.assertTrue(x3 < x1) + self.assertTrue(x3 < x4) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x4.valid()) + self.assertTrue(x5.valid()) + self.assertTrue(x6.valid()) + + def testSign(self): + x0 = amount() + x1 = amount("0.0000000000000000000000000000000000001") + x2 = amount("-0.0000000000000000000000000000000000001") + x3 = amount("1") + x4 = amount("-1") + + self.assertTrue(not x0.sign()) + self.assertTrue(x1.sign() > 0) + self.assertTrue(x2.sign() < 0) + self.assertTrue(x3.sign() > 0) + self.assertTrue(x4.sign() < 0) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + self.assertTrue(x3.valid()) + self.assertTrue(x4.valid()) + + def testAbs(self): + x0 = amount() + x1 = amount(-1234) + x2 = amount(1234) + + self.assertEqual(amount(), abs(x0)) + self.assertEqual(amount(1234), abs(x1)) + self.assertEqual(amount(1234), abs(x2)) + + self.assertTrue(x0.valid()) + self.assertTrue(x1.valid()) + self.assertTrue(x2.valid()) + + def testPrinting(self): + pass + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(BasicAmountTestCase) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python/numerics/CommodityAmount.py b/tests/python/numerics/CommodityAmount.py new file mode 100644 index 00000000..80f58b21 --- /dev/null +++ b/tests/python/numerics/CommodityAmount.py @@ -0,0 +1,623 @@ +# -*- coding: utf-8 -*- + +import unittest +import exceptions +import operator + +from ledger import amount + +internalAmount = amount.exact + +class CommodityAmountTestCase(unittest.TestCase): + def setUp(self): + # Cause the display precision for dollars to be initialized to 2. + x1 = amount("$1.00") + self.assertTrue(x1) + amount.full_strings = True # makes error reports from UnitTests accurate + + def tearDown(self): + amount.full_strings = False + + def assertValid(self, amt): + self.assertTrue(amt.valid()) + + def testConstructors(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 testNegation(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 testAssignment(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): + 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.null()) + self.assertTrue(x0.zero()) + self.assertTrue(x0.realzero()) + self.assertTrue(x0.sign() == 0) + self.assertTrue(x0.compare(x1) < 0) + self.assertTrue(x0.compare(x2) > 0) + self.assertTrue(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 testAddition(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, operator.add, x1, x0) + self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x3) + self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x4) + self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x5) + self.assertRaises(exceptions.ArithmeticError, operator.add, x1, x6) + self.assertRaises(exceptions.ArithmeticError, operator.add, x1, 123.45) + self.assertRaises(exceptions.ArithmeticError, operator.add, 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 testSubtraction(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, operator.sub, x1, x0) + self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x3) + self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x4) + self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x5) + self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, x6) + self.assertRaises(exceptions.ArithmeticError, operator.sub, x1, 123.45) + self.assertRaises(exceptions.ArithmeticError, operator.sub, 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 testMultiplication(self): + 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, operator.mul, x1, x3) + self.assertRaises(exceptions.ArithmeticError, operator.mul, x1, x4) + self.assertRaises(exceptions.ArithmeticError, operator.mul, x1, x5) + + x1 *= amount("123.12") + self.assertEqual(internalAmount("$15158.5344"), x1) + self.assertEqual("$15158.53", x1.to_string()) + x1 *= 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 testDivision(self): + 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, operator.div, 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, operator.div, x1, x3) + self.assertRaises(exceptions.ArithmeticError, operator.div, x1, x4) + self.assertRaises(exceptions.ArithmeticError, operator.div, x1, x5) + + x1 /= amount("123.12") + self.assertEqual(internalAmount("$1.00"), x1) + self.assertEqual("$1.00", x1.to_string()) + x1 /= 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 testConversion(self): + x1 = amount("$1234.56") + + self.assertEqual(True, bool(x1)) + self.assertEqual(1234, int(x1)) + self.assertEqual(1234.56, float(x1)) + self.assertEqual("$1234.56", x1.to_string()) + self.assertEqual("1234.56", x1.quantity_string()) + + self.assertValid(x1) + + def testRound(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)) + + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + + def testDisplayRound(self): + x1 = amount("$0.85") + x2 = amount("$0.1") + + x1 *= 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 testTruth(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): + x1 = amount(internalAmount("$0.000000000000000000001")) + + self.assertFalse(x1) + self.assertTrue(x1.zero()) + self.assertFalse(x1.realzero()) + + self.assertValid(x1) + + def testComparisons(self): + x0 = amount() + 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") + + self.assertTrue(x0 > x1) + self.assertTrue(x0 < x2) + self.assertTrue(x0 > x3) + self.assertTrue(x0 < x4) + self.assertTrue(x0 > x5) + self.assertTrue(x0 < x6) + + 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.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + self.assertValid(x3) + self.assertValid(x4) + self.assertValid(x5) + self.assertValid(x6) + + def testSign(self): + x0 = amount() + x1 = amount(internalAmount("$0.0000000000000000000000000000000000001")) + x2 = amount(internalAmount("$-0.0000000000000000000000000000000000001")) + x3 = amount("$1") + x4 = amount("$-1") + + self.assertFalse(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 testAbs(self): + x0 = amount() + x1 = amount("$-1234.56") + x2 = amount("$1234.56") + + self.assertEqual(amount(), abs(x0)) + self.assertEqual(amount("$1234.56"), abs(x1)) + self.assertEqual(amount("$1234.56"), abs(x2)) + + self.assertValid(x0) + self.assertValid(x1) + self.assertValid(x2) + + def testPrinting(self): + pass + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(CommodityAmountTestCase) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python/numerics/__init__.py b/tests/python/numerics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/python/numerics/balances/__init__.py b/tests/python/numerics/balances/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/python/numerics/values/__init__.py b/tests/python/numerics/values/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/textual.cc b/textual.cc deleted file mode 100644 index ab657898..00000000 --- a/textual.cc +++ /dev/null @@ -1,966 +0,0 @@ -#include "textual.h" -#include "session.h" - -#define TIMELOG_SUPPORT 1 - -namespace ledger { - -#define MAX_LINE 1024 - -static string path; -static unsigned int linenum; -static unsigned int src_idx; -static accounts_map account_aliases; - -static std::list > include_stack; - -#ifdef TIMELOG_SUPPORT -struct time_entry_t { - moment_t checkin; - account_t * account; - string desc; -}; -std::list time_entries; -#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 inline void -parse_amount_expr(std::istream& in, journal_t *, - transaction_t& xact, amount_t& amount, - unsigned short flags = 0) -{ - xml::xpath_t xpath(in, flags | XPATH_PARSE_RELAXED | XPATH_PARSE_PARTIAL); - - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Parsed an amount expression"); - -#if 0 - IF_DEBUG_("ledger.textual.parse") { - if (_debug_stream) { - xpath.dump(*_debug_stream); - *_debug_stream << std::endl; - } - } -#endif - - amount = xpath.calc(static_cast(xact.data)).to_amount(); - - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "The transaction amount is " << amount); -} - -transaction_t * parse_transaction(char * line, - journal_t * journal, - account_t * account, - entry_t * entry = NULL) -{ - // The account will be determined later... - std::auto_ptr xact(new transaction_t(NULL)); - - // First cut up the input line into its various parts. - - char * state = NULL; - char * account_path = NULL; - char * amount = NULL; - char * note = NULL; - - char * p = line; - - if (*p == '*' || *p == '!') - state = p++; - - account_path = skip_ws(p); - - amount = next_element(account_path, true); - if (amount) { - char * p = amount; - while (*p && *p != ';') - p++; - - if (*p == ';') { - *p++ = '\0'; - note = skip_ws(p); - } - - p = amount + (std::strlen(amount) - 1); - while (p > amount && std::isspace(*p)) - p--; - - if (std::isspace(*(p + 1))) - *++p = '\0'; - } - - string err_desc; -#if 0 - try { -#endif - - xact->entry = entry; // this might be NULL - - // Parse the state flag - - if (state) - switch (*state) { - case '*': - xact->state = transaction_t::CLEARED; - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Parsed the CLEARED flag"); - break; - case '!': - xact->state = transaction_t::PENDING; - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Parsed the PENDING flag"); - break; - } - - // Parse the account name - - char * b = &account_path[0]; - char * e = &account_path[std::strlen(account_path) - 1]; - if ((*b == '[' && *e == ']') || - (*b == '(' && *e == ')')) { - xact->flags |= TRANSACTION_VIRTUAL; - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Parsed a virtual account name"); - if (*b == '[') { - xact->flags |= TRANSACTION_BALANCE; - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Parsed a balanced virtual account name"); - } - *account_path++ = '\0'; - *e = '\0'; - } - - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Parsed account name " << account_path); - if (account_aliases.size() > 0) { - accounts_map::const_iterator i = account_aliases.find(account_path); - if (i != account_aliases.end()) - xact->account = (*i).second; - } - if (! xact->account) - xact->account = account->find_account(account_path); - - // Parse the optional amount - - if (amount && *amount) { - std::istringstream in(amount); - - try { - // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol - - unsigned long beg = (long)in.tellg(); - - xact->amount.parse(in, AMOUNT_PARSE_NO_REDUCE); - - char c; - if (! in.eof() && (c = peek_next_nonws(in)) != '@' && - c != ';' && ! in.eof()) { - in.seekg(beg, std::ios::beg); - - if (xact->entry) { - // Create a report item for this entry, so the transaction - // below may refer to it - - if (! xact->entry->data) - xact->entry->data = xml::wrap_node(journal->document, xact->entry, - journal->document->top); - - xact->data = xml::wrap_node(journal->document, xact.get(), - xact->entry->data); - } - - parse_amount_expr(in, journal, *xact, xact->amount, - XPATH_PARSE_NO_REDUCE); - - if (xact->entry) { - delete static_cast(xact->data); - xact->data = NULL; - } - - unsigned long end = (long)in.tellg(); - - xact->amount_expr = string(line, beg, end - beg); - } - } - catch (exception& err) { - err_desc = "While parsing transaction amount:"; - throw; - } - - // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST) - - if (in.good() && ! in.eof()) { - char c = peek_next_nonws(in); - if (c == '@') { - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Found a price indicator"); - bool per_unit = true; - in.get(c); - if (in.peek() == '@') { - in.get(c); - per_unit = false; - DEBUG_("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(); - - xact->cost->parse(in); - - unsigned long end = (long)in.tellg(); - - if (per_unit) - xact->cost_expr = (string("@") + - string(amount, beg, end - beg)); - else - xact->cost_expr = (string("@@") + - string(amount, beg, end - beg)); - } - catch (exception& err) { - err_desc = "While parsing transaction cost:"; - throw; - } - - if (*xact->cost < 0) - throw_(parse_exception, "A transaction's cost may not be negative"); - - amount_t per_unit_cost(*xact->cost); - if (per_unit) - *xact->cost *= xact->amount.number(); - else - per_unit_cost /= xact->amount.number(); - - if (xact->amount.commodity() && - ! xact->amount.commodity().annotated) - xact->amount.annotate_commodity(per_unit_cost, - xact->entry->actual_date(), - xact->entry->code); - - 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); - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Bare amount is " << xact->amount.number()); - } - } - } - - xact->amount.in_place_reduce(); - - DEBUG_("ledger.textual.parse", "line " << linenum << ": " << - "Reduced amount is " << xact->amount); - } - - // Parse the optional note - - if (note) { - xact->note = note; - 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_datetime(p); - } - if (buf[0]) - xact->_date = parse_datetime(buf); - } - } - - return xact.release(); - -#if 0 - } - catch (error * err) { - err->context.push_back - (new line_context(line, -1, ! err_desc.empty() ? - err_desc : "While parsing transaction:")); - throw err; - } -#endif -} - -bool parse_transactions(std::istream& in, - journal_t * journal, - account_t * account, - entry_base_t& entry, - const 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; - - beg_pos += std::strlen(line) + 1; - linenum++; - - char * p = skip_ws(line); - if (! *p || *p == '\r' || *p == '\n') - break; - - if (transaction_t * xact = parse_transaction(p, journal, account)) { - entry.add_transaction(xact); - added = true; - } - } - - return added; -} - -entry_t * parse_entry(std::istream& in, char * line, journal_t * journal, - account_t * master, textual_parser_t& /* parser */, - unsigned long beg_pos) -{ - TRACE_START(entry_text, 1, "Time spent preparing entry text:"); - - std::auto_ptr curr(new entry_t); - - // First cut up the input line into its various parts. - - char * date = NULL; - char * date_eff = NULL; - char * statep = NULL; - char * code = NULL; - char * payee = NULL; - - date = line; - - char * p = line; - - while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) - p++; - assert(*p); - - if (*p == '=') { - *p++ = '\0'; - date_eff = p; - - while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-')) - p++; - assert(*p); - } else { - *p++ = '\0'; - } - - p = skip_ws(p); - - if (*p == '*' || *p == '!') { - statep = p; - p++; *p++ = '\0'; - - p = skip_ws(p); - } - - if (*p == '(') { - code = ++p; - while (*p && *p != ')') - p++; - assert(*p); - *p++ = '\0'; - - p = skip_ws(p); - } - - payee = p; - - p = payee + (std::strlen(payee) - 1); - while (p > payee && std::isspace(*p)) - p--; - - if (std::isspace(*(p + 1))) - *++p = '\0'; - - TRACE_STOP(entry_text, 1); - - // Parse the date - - TRACE_START(entry_date, 1, "Time spent parsing entry dates:"); - - curr->_date = parse_datetime(date); - - if (date_eff) - curr->_date_eff = parse_datetime(date_eff); - - TRACE_STOP(entry_date, 1); - - // Parse the optional cleared flag: * - - TRACE_START(entry_details, 1, "Time spent parsing entry details:"); - - transaction_t::state_t state = transaction_t::UNCLEARED; - if (statep) { - switch (*statep) { - case '*': - state = transaction_t::CLEARED; - break; - case '!': - state = transaction_t::PENDING; - break; - } - } - - // Parse the optional code: (TEXT) - - if (code) - curr->code = code; - - // Parse the payee/description text - - assert(payee); - curr->payee = *payee != '\0' ? payee : ""; - - TRACE_STOP(entry_details, 1); - - // Parse all of the transactions associated with this entry - - TRACE_START(entry_xacts, 1, "Time spent parsing transactions:"); - - unsigned long end_pos; - unsigned long beg_line = linenum; - - while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { - line[0] = '\0'; - in.getline(line, MAX_LINE); - if (in.eof() || line[0] == '\0') - break; - end_pos = beg_pos + std::strlen(line) + 1; - linenum++; - - char * p = skip_ws(line); - if (! *p || *p == '\r' || *p == '\n') - break; - - if (transaction_t * xact = parse_transaction(p, journal, 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; - beg_pos = end_pos; - - curr->add_transaction(xact); - } - - if (in.eof()) - break; - } - - if (curr->data) { - delete static_cast(curr->data); - curr->data = NULL; - } - - TRACE_STOP(entry_xacts, 1); - - return curr.release(); -} - -template -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, string& symbol) -{ - if (*p == '"') { - char * q = std::strchr(p + 1, '"'); - if (! q) - throw_(parse_exception, "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_exception, "Failed to parse commodity"); -} - -bool textual_parser_t::test(std::istream& in) const -{ - char buf[5]; - - in.read(buf, 5); - if (std::strncmp(buf, "::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_exception, - "Timelog check-out event does not match any current check-ins"); - } - - if (desc && event.desc.empty()) { - event.desc = desc; - desc = NULL; - } - - std::auto_ptr curr(new entry_t); - curr->_date = when; - curr->code = desc ? desc : ""; - curr->payee = event.desc; - - if (curr->_date < event.checkin) - throw_(parse_exception, - "Timelog check-out date less than corresponding check-in"); - - char buf[32]; - std::sprintf(buf, "%lds", (long)(curr->_date - event.checkin).total_seconds()); - 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_(parse_exception, "Failed to record 'out' timelog entry"); - else - curr.release(); -} - -unsigned int textual_parser_t::parse(std::istream& in, - journal_t * journal, - account_t * master, - const string * original_file) -{ - static bool added_auto_entry_hook = false; - static char line[MAX_LINE + 1]; - unsigned int count = 0; - unsigned int errors = 0; - - TRACE_START(parsing_total, 1, "Total time spent parsing text:"); - - std::list account_stack; - - auto_entry_finalizer_t auto_entry_finalizer(journal); - - if (! master && journal) - master = journal->master; - - account_stack.push_front(master); - - path = journal ? journal->sources.back() : *original_file; - src_idx = journal ? journal->sources.size() - 1 : 0; - linenum = 1; - - INFO("Parsing file '" << path << "'"); - - unsigned long beg_pos = in.tellg(); - unsigned long end_pos; - unsigned long beg_line = linenum; - - while (in.good() && ! in.eof()) { -#if 0 - try { -#endif - in.getline(line, MAX_LINE); - if (in.eof()) - break; - end_pos = beg_pos + std::strlen(line) + 1; - linenum++; - - switch (line[0]) { - case '\0': - case '\r': - break; - - case ' ': - case '\t': { - char * p = skip_ws(line); - if (*p && *p != '\r') - throw_(parse_exception, "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; - event.desc = n ? n : ""; - event.checkin = parse_datetime(date); - event.account = account_stack.front()->find_account(p); - - if (! time_entries.empty()) - for (std::list::iterator i = time_entries.begin(); - i != time_entries.end(); - i++) - if (event.account == (*i).account) - throw_(parse_exception, - "Cannot double check-in to the same account"); - - time_entries.push_back(event); - break; - } - - case 'o': - case 'O': - if (time_entries.empty()) { - throw_(parse_exception, "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 - (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)); - 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; - string date_field = date_field_ptr; - - char * symbol_and_price; - moment_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); - - 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); - 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 current year -#if 0 - // jww (2007-04-18): Need to set this up again - date_t::current_year = std::atoi(skip_ws(line + 1)); -#endif - break; - -#ifdef TIMELOG_SUPPORT - case 'h': - case 'b': -#endif - case ';': // comment - break; - - case '-': // option setting - throw_(parse_exception, "Option settings are not allowed in journal files"); - - 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, journal, 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_exception, string("Parsing time period '") + skip_ws(line + 1) + "'"); - - if (parse_transactions(in, journal, 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_(parse_exception, "Period entry failed to balance"); - } - } - break; - } - - case '@': - case '!': { // directive - char * p = next_element(line); - string word(line + 1); - if (word == "include") { - push_var save_path(path); - push_var save_src_idx(src_idx); - push_var save_beg_pos(beg_pos); - push_var save_end_pos(end_pos); - push_var save_linenum(linenum); - - path = p; - if (path[0] != '/' && path[0] != '\\' && path[0] != '~') { - string::size_type pos = save_path.prev.rfind('/'); - if (pos == string::npos) - pos = save_path.prev.rfind('\\'); - if (pos != string::npos) - path = string(save_path.prev, 0, pos + 1) + path; - } - path = resolve_path(path); - - DEBUG_("ledger.textual.include", "line " << linenum << ": " << - "Including path '" << path << "'"); - - include_stack.push_back(std::pair - (journal->sources.back(), linenum - 1)); - count += journal->session->read_journal(path, 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 result - = account_aliases.insert(accounts_pair(b, acct)); - assert(result.second); - } - } - else if (word == "def" || word == "eval") { - // jww (2006-09-13): Read the string after and evaluate it. - // But also keep a list of these value expressions, and a - // way to know where they fall in the transaction sequence. - // This will be necessary so that binary file reading can - // re-evaluate them at the appopriate time. - - // compile(&journal->defs); - } - break; - } - - default: { - //unsigned int first_line = linenum; - unsigned long pos = end_pos; - - TRACE_START(entries, 1, "Time spent handling entries:"); - if (entry_t * entry = parse_entry(in, line, journal, - 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 = end_pos; - entry->end_line = linenum; - count++; - } else { - delete entry; - throw_(parse_exception, "Entry does not balance"); - } - } else { - throw_(parse_exception, "Failed to parse entry"); - } - TRACE_STOP(entries, 1); - - end_pos = pos; - break; - } - } -#if 0 - } - catch (error * err) { - for (std::list >::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++; - } -#endif - beg_pos = end_pos; - } - - if (! time_entries.empty()) { - for (std::list::iterator i = time_entries.begin(); - i != time_entries.end(); - i++) - clock_out_from_timelog(now, (*i).account, NULL, journal); - time_entries.clear(); - } - - if (added_auto_entry_hook) - journal->remove_entry_finalizer(&auto_entry_finalizer); - - if (errors > 0) - throw (int)errors; - - TRACE_STOP(parsing_total, 1); - - return count; -} - -} // namespace ledger diff --git a/textual.h b/textual.h deleted file mode 100644 index bf05b1fc..00000000 --- a/textual.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef _TEXTUAL_H -#define _TEXTUAL_H - -#include "parser.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, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); -}; - -#if 0 -void write_textual_journal(journal_t& journal, string path, - item_handler& formatter, - const string& write_hdr_format, - std::ostream& out); -#endif - -#if 0 -class include_context : public file_context { - public: - include_context(const string& 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 << "\", line " << line << ":" << std::endl; - } -}; -#endif - -} // namespace ledger - -#endif // _TEXTUAL_H diff --git a/timeclock.el b/timeclock.el deleted file mode 100644 index 03159e94..00000000 --- a/timeclock.el +++ /dev/null @@ -1,1362 +0,0 @@ -;;; timeclock.el --- mode for keeping track of how much you work - -;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Free Software Foundation, Inc. - -;; Author: John Wiegley -;; Created: 25 Mar 1999 -;; Version: 2.6 -;; Keywords: calendar data - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 2, or (at your option) -;; any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. - -;;; Commentary: - -;; This mode is for keeping track of time intervals. You can use it -;; for whatever purpose you like, but the typical scenario is to keep -;; track of how much time you spend working on certain projects. -;; -;; Use `timeclock-in' when you start on a project, and `timeclock-out' -;; when you're done. Once you've collected some data, you can use -;; `timeclock-workday-remaining' to see how much time is left to be -;; worked today (where `timeclock-workday' specifies the length of the -;; working day), and `timeclock-when-to-leave' to calculate when you're free. - -;; You'll probably want to bind the timeclock commands to some handy -;; keystrokes. At the moment, C-x t is unused: -;; -;; (require 'timeclock) -;; -;; (define-key ctl-x-map "ti" 'timeclock-in) -;; (define-key ctl-x-map "to" 'timeclock-out) -;; (define-key ctl-x-map "tc" 'timeclock-change) -;; (define-key ctl-x-map "tr" 'timeclock-reread-log) -;; (define-key ctl-x-map "tu" 'timeclock-update-modeline) -;; (define-key ctl-x-map "tw" 'timeclock-when-to-leave-string) - -;; If you want Emacs to display the amount of time "left" to your -;; workday in the modeline, you can either set the value of -;; `timeclock-modeline-display' to t using M-x customize, or you -;; can add this code to your .emacs file: -;; -;; (require 'timeclock) -;; (timeclock-modeline-display) -;; -;; To cancel this modeline display at any time, just call -;; `timeclock-modeline-display' again. - -;; You may also want Emacs to ask you before exiting, if you are -;; currently working on a project. This can be done either by setting -;; `timeclock-ask-before-exiting' to t using M-x customize (this is -;; the default), or by adding the following to your .emacs file: -;; -;; (add-hook 'kill-emacs-query-functions 'timeclock-query-out) - -;; NOTE: If you change your .timelog file without using timeclock's -;; functions, or if you change the value of any of timeclock's -;; customizable variables, you should run the command -;; `timeclock-reread-log'. This will recompute any discrepancies in -;; your average working time, and will make sure that the various -;; display functions return the correct value. - -;;; History: - -;;; Code: - -(defgroup timeclock nil - "Keeping track time of the time that gets spent." - :group 'data) - -;;; User Variables: - -(defcustom timeclock-file (convert-standard-filename "~/.timelog") - "*The file used to store timeclock data in." - :type 'file - :group 'timeclock) - -(defcustom timeclock-workday (* 8 60 60) - "*The length of a work period." - :type 'integer - :group 'timeclock) - -(defcustom timeclock-relative t - "*Whether to maken reported time relative to `timeclock-workday'. -For example, if the length of a normal workday is eight hours, and you -work four hours on Monday, then the amount of time \"remaining\" on -Tuesday is twelve hours -- relative to an averaged work period of -eight hours -- or eight hours, non-relative. So relative time takes -into account any discrepancy of time under-worked or over-worked on -previous days. This only affects the timeclock modeline display." - :type 'boolean - :group 'timeclock) - -(defcustom timeclock-get-project-function 'timeclock-ask-for-project - "*The function used to determine the name of the current project. -When clocking in, and no project is specified, this function will be -called to determine what is the current project to be worked on. -If this variable is nil, no questions will be asked." - :type 'function - :group 'timeclock) - -(defcustom timeclock-get-reason-function 'timeclock-ask-for-reason - "*A function used to determine the reason for clocking out. -When clocking out, and no reason is specified, this function will be -called to determine what is the reason. -If this variable is nil, no questions will be asked." - :type 'function - :group 'timeclock) - -(defcustom timeclock-get-workday-function nil - "*A function used to determine the length of today's workday. -The first time that a user clocks in each day, this function will be -called to determine what is the length of the current workday. If -the return value is nil, or equal to `timeclock-workday', nothing special -will be done. If it is a quantity different from `timeclock-workday', -however, a record will be output to the timelog file to note the fact that -that day has a length that is different from the norm." - :type '(choice (const nil) function) - :group 'timeclock) - -(defcustom timeclock-ask-before-exiting t - "*If non-nil, ask if the user wants to clock out before exiting Emacs. -This variable only has effect if set with \\[customize]." - :set (lambda (symbol value) - (if value - (add-hook 'kill-emacs-query-functions 'timeclock-query-out) - (remove-hook 'kill-emacs-query-functions 'timeclock-query-out)) - (setq timeclock-ask-before-exiting value)) - :type 'boolean - :group 'timeclock) - -(defvar timeclock-update-timer nil - "The timer used to update `timeclock-mode-string'.") - -;; For byte-compiler. -(defvar display-time-hook) -(defvar timeclock-modeline-display) - -(defcustom timeclock-use-display-time t - "*If non-nil, use `display-time-hook' for doing modeline updates. -The advantage of this is that one less timer has to be set running -amok in Emacs' process space. The disadvantage is that it requires -you to have `display-time' running. If you don't want to use -`display-time', but still want the modeline to show how much time is -left, set this variable to nil. Changing the value of this variable -while timeclock information is being displayed in the modeline has no -effect. You should call the function `timeclock-modeline-display' with -a positive argument to force an update." - :set (lambda (symbol value) - (let ((currently-displaying - (and (boundp 'timeclock-modeline-display) - timeclock-modeline-display))) - ;; if we're changing to the state that - ;; `timeclock-modeline-display' is already using, don't - ;; bother toggling it. This happens on the initial loading - ;; of timeclock.el. - (if (and currently-displaying - (or (and value - (boundp 'display-time-hook) - (memq 'timeclock-update-modeline - display-time-hook)) - (and (not value) - timeclock-update-timer))) - (setq currently-displaying nil)) - (and currently-displaying - (set-variable 'timeclock-modeline-display nil)) - (setq timeclock-use-display-time value) - (and currently-displaying - (set-variable 'timeclock-modeline-display t)) - timeclock-use-display-time)) - :type 'boolean - :group 'timeclock - :require 'time) - -(defcustom timeclock-first-in-hook nil - "*A hook run for the first \"in\" event each day. -Note that this hook is run before recording any events. Thus the -value of `timeclock-hours-today', `timeclock-last-event' and the -return value of function `timeclock-last-period' are relative previous -to today." - :type 'hook - :group 'timeclock) - -(defcustom timeclock-load-hook nil - "*Hook that gets run after timeclock has been loaded." - :type 'hook - :group 'timeclock) - -(defcustom timeclock-in-hook nil - "*A hook run every time an \"in\" event is recorded." - :type 'hook - :group 'timeclock) - -(defcustom timeclock-day-over-hook nil - "*A hook that is run when the workday has been completed. -This hook is only run if the current time remaining is being displayed -in the modeline. See the variable `timeclock-modeline-display'." - :type 'hook - :group 'timeclock) - -(defcustom timeclock-out-hook nil - "*A hook run every time an \"out\" event is recorded." - :type 'hook - :group 'timeclock) - -(defcustom timeclock-done-hook nil - "*A hook run every time a project is marked as completed." - :type 'hook - :group 'timeclock) - -(defcustom timeclock-event-hook nil - "*A hook run every time any event is recorded." - :type 'hook - :group 'timeclock) - -(defvar timeclock-last-event nil - "A list containing the last event that was recorded. -The format of this list is (CODE TIME PROJECT).") - -(defvar timeclock-last-event-workday nil - "The number of seconds in the workday of `timeclock-last-event'.") - -;;; Internal Variables: - -(defvar timeclock-discrepancy nil - "A variable containing the time discrepancy before the last event. -Normally, timeclock assumes that you intend to work for -`timeclock-workday' seconds every day. Any days in which you work -more or less than this amount is considered either a positive or -a negative discrepancy. If you work in such a manner that the -discrepancy is always brought back to zero, then you will by -definition have worked an average amount equal to `timeclock-workday' -each day.") - -(defvar timeclock-elapsed nil - "A variable containing the time elapsed for complete periods today. -This value is not accurate enough to be useful by itself. Rather, -call `timeclock-workday-elapsed', to determine how much time has been -worked so far today. Also, if `timeclock-relative' is nil, this value -will be the same as `timeclock-discrepancy'.") ; ? gm - -(defvar timeclock-last-period nil - "Integer representing the number of seconds in the last period. -Note that you shouldn't access this value, but instead should use the -function `timeclock-last-period'.") - -(defvar timeclock-mode-string nil - "The timeclock string (optionally) displayed in the modeline. -The time is bracketed by <> if you are clocked in, otherwise by [].") - -(defvar timeclock-day-over nil - "The date of the last day when notified \"day over\" for.") - -;;; User Functions: - -;;;###autoload -(defun timeclock-modeline-display (&optional arg) - "Toggle display of the amount of time left today in the modeline. -If `timeclock-use-display-time' is non-nil (the default), then -the function `display-time-mode' must be active, and the modeline -will be updated whenever the time display is updated. Otherwise, -the timeclock will use its own sixty second timer to do its -updating. With prefix ARG, turn modeline display on if and only -if ARG is positive. Returns the new status of timeclock modeline -display (non-nil means on)." - (interactive "P") - ;; cf display-time-mode. - (setq timeclock-mode-string "") - (or global-mode-string (setq global-mode-string '(""))) - (let ((on-p (if arg - (> (prefix-numeric-value arg) 0) - (not timeclock-modeline-display)))) - (if on-p - (progn - (or (memq 'timeclock-mode-string global-mode-string) - (setq global-mode-string - (append global-mode-string '(timeclock-mode-string)))) - (unless (memq 'timeclock-update-modeline timeclock-event-hook) - (add-hook 'timeclock-event-hook 'timeclock-update-modeline)) - (when timeclock-update-timer - (cancel-timer timeclock-update-timer) - (setq timeclock-update-timer nil)) - (if (boundp 'display-time-hook) - (remove-hook 'display-time-hook 'timeclock-update-modeline)) - (if timeclock-use-display-time - (progn - ;; Update immediately so there is a visible change - ;; on calling this function. - (if display-time-mode (timeclock-update-modeline) - (message "Activate `display-time-mode' to see \ -timeclock information")) - (add-hook 'display-time-hook 'timeclock-update-modeline)) - (setq timeclock-update-timer - (run-at-time nil 60 'timeclock-update-modeline)))) - (setq global-mode-string - (delq 'timeclock-mode-string global-mode-string)) - (remove-hook 'timeclock-event-hook 'timeclock-update-modeline) - (if (boundp 'display-time-hook) - (remove-hook 'display-time-hook - 'timeclock-update-modeline)) - (when timeclock-update-timer - (cancel-timer timeclock-update-timer) - (setq timeclock-update-timer nil))) - (force-mode-line-update) - (setq timeclock-modeline-display on-p))) - -;; This has to be here so that the function definition of -;; `timeclock-modeline-display' is known to the "set" function. -(defcustom timeclock-modeline-display nil - "Toggle modeline display of time remaining. -You must modify via \\[customize] for this variable to have an effect." - :set (lambda (symbol value) - (setq timeclock-modeline-display - (timeclock-modeline-display (or value 0)))) - :type 'boolean - :group 'timeclock - :require 'timeclock) - -(defsubst timeclock-time-to-date (time) - "Convert the TIME value to a textual date string." - (format-time-string "%Y/%m/%d" time)) - -;;;###autoload -(defun timeclock-in (&optional arg project find-project) - "Clock in, recording the current time moment in the timelog. -With a numeric prefix ARG, record the fact that today has only that -many hours in it to be worked. If arg is a non-numeric prefix arg -\(non-nil, but not a number), 0 is assumed (working on a holiday or -weekend). *If not called interactively, ARG should be the number of -_seconds_ worked today*. This feature only has effect the first time -this function is called within a day. - -PROJECT is the project being clocked into. If PROJECT is nil, and -FIND-PROJECT is non-nil -- or the user calls `timeclock-in' -interactively -- call the function `timeclock-get-project-function' to -discover the name of the project." - (interactive - (list (and current-prefix-arg - (if (numberp current-prefix-arg) - (* current-prefix-arg 60 60) - 0)))) - (if (equal (car timeclock-last-event) "i") - (error "You've already clocked in!") - (unless timeclock-last-event - (timeclock-reread-log)) - ;; Either no log file, or day has rolled over. - (unless (and timeclock-last-event - (equal (timeclock-time-to-date - (cadr timeclock-last-event)) - (timeclock-time-to-date (current-time)))) - (let ((workday (or (and (numberp arg) arg) - (and arg 0) - (and timeclock-get-workday-function - (funcall timeclock-get-workday-function)) - timeclock-workday))) - (run-hooks 'timeclock-first-in-hook) - ;; settle the discrepancy for the new day - (setq timeclock-discrepancy - (- (or timeclock-discrepancy 0) workday)) - (if (not (= workday timeclock-workday)) - (timeclock-log "h" (and (numberp arg) - (number-to-string arg)))))) - (timeclock-log "i" (or project - (and timeclock-get-project-function - (or find-project (interactive-p)) - (funcall timeclock-get-project-function)))) - (run-hooks 'timeclock-in-hook))) - -;;;###autoload -(defun timeclock-out (&optional arg reason find-reason) - "Clock out, recording the current time moment in the timelog. -If a prefix ARG is given, the user has completed the project that was -begun during the last time segment. - -REASON is the user's reason for clocking out. If REASON is nil, and -FIND-REASON is non-nil -- or the user calls `timeclock-out' -interactively -- call the function `timeclock-get-reason-function' to -discover the reason." - (interactive "P") - (or timeclock-last-event - (error "You haven't clocked in!")) - (if (equal (downcase (car timeclock-last-event)) "o") - (error "You've already clocked out!") - (timeclock-log - (if arg "O" "o") - (or reason - (and timeclock-get-reason-function - (or find-reason (interactive-p)) - (funcall timeclock-get-reason-function)))) - (run-hooks 'timeclock-out-hook) - (if arg - (run-hooks 'timeclock-done-hook)))) - -;; Should today-only be removed in favour of timeclock-relative? - gm -(defsubst timeclock-workday-remaining (&optional today-only) - "Return the number of seconds until the workday is complete. -The amount returned is relative to the value of `timeclock-workday'. -If TODAY-ONLY is non-nil, the value returned will be relative only to -the time worked today, and not to past time." - (let ((discrep (timeclock-find-discrep))) - (if discrep - (- (if today-only (cadr discrep) - (car discrep))) - 0.0))) - -;;;###autoload -(defun timeclock-status-string (&optional show-seconds today-only) - "Report the overall timeclock status at the present moment. -If SHOW-SECONDS is non-nil, display second resolution. -If TODAY-ONLY is non-nil, the display will be relative only to time -worked today, ignoring the time worked on previous days." - (interactive "P") - (let ((remainder (timeclock-workday-remaining)) ; today-only? - (last-in (equal (car timeclock-last-event) "i")) - status) - (setq status - (format "Currently %s since %s (%s), %s %s, leave at %s" - (if last-in "IN" "OUT") - (if show-seconds - (format-time-string "%-I:%M:%S %p" - (nth 1 timeclock-last-event)) - (format-time-string "%-I:%M %p" - (nth 1 timeclock-last-event))) - (or (nth 2 timeclock-last-event) - (if last-in "**UNKNOWN**" "workday over")) - (timeclock-seconds-to-string remainder show-seconds t) - (if (> remainder 0) - "remaining" "over") - (timeclock-when-to-leave-string show-seconds today-only))) - (if (interactive-p) - (message status) - status))) - -;;;###autoload -(defun timeclock-change (&optional arg project) - "Change to working on a different project. -This clocks out of the current project, then clocks in on a new one. -With a prefix ARG, consider the previous project as finished at the -time of changeover. PROJECT is the name of the last project you were -working on." - (interactive "P") - (timeclock-out arg) - (timeclock-in nil project (interactive-p))) - -;;;###autoload -(defun timeclock-query-out () - "Ask the user whether to clock out. -This is a useful function for adding to `kill-emacs-query-functions'." - (and (equal (car timeclock-last-event) "i") - (y-or-n-p "You're currently clocking time, clock out? ") - (timeclock-out)) - ;; Unconditionally return t for `kill-emacs-query-functions'. - t) - -;;;###autoload -(defun timeclock-reread-log () - "Re-read the timeclock, to account for external changes. -Returns the new value of `timeclock-discrepancy'." - (interactive) - (setq timeclock-discrepancy nil) - (timeclock-find-discrep) - (if (and timeclock-discrepancy timeclock-modeline-display) - (timeclock-update-modeline)) - timeclock-discrepancy) - -(defun timeclock-seconds-to-string (seconds &optional show-seconds - reverse-leader) - "Convert SECONDS into a compact time string. -If SHOW-SECONDS is non-nil, make the resolution of the return string -include the second count. If REVERSE-LEADER is non-nil, it means to -output a \"+\" if the time value is negative, rather than a \"-\". -This is used when negative time values have an inverted meaning (such -as with time remaining, where negative time really means overtime)." - (if show-seconds - (format "%s%d:%02d:%02d" - (if (< seconds 0) (if reverse-leader "+" "-") "") - (truncate (/ (abs seconds) 60 60)) - (% (truncate (/ (abs seconds) 60)) 60) - (% (truncate (abs seconds)) 60)) - (format "%s%d:%02d" - (if (< seconds 0) (if reverse-leader "+" "-") "") - (truncate (/ (abs seconds) 60 60)) - (% (truncate (/ (abs seconds) 60)) 60)))) - -(defsubst timeclock-currently-in-p () - "Return non-nil if the user is currently clocked in." - (equal (car timeclock-last-event) "i")) - -;;;###autoload -(defun timeclock-workday-remaining-string (&optional show-seconds - today-only) - "Return a string representing the amount of time left today. -Display second resolution if SHOW-SECONDS is non-nil. If TODAY-ONLY -is non-nil, the display will be relative only to time worked today. -See `timeclock-relative' for more information about the meaning of -\"relative to today\"." - (interactive) - (let ((string (timeclock-seconds-to-string - (timeclock-workday-remaining today-only) - show-seconds t))) - (if (interactive-p) - (message string) - string))) - -(defsubst timeclock-workday-elapsed () - "Return the number of seconds worked so far today. -If RELATIVE is non-nil, the amount returned will be relative to past -time worked. The default is to return only the time that has elapsed -so far today." - (let ((discrep (timeclock-find-discrep))) - (if discrep - (nth 2 discrep) - 0.0))) - -;;;###autoload -(defun timeclock-workday-elapsed-string (&optional show-seconds) - "Return a string representing the amount of time worked today. -Display seconds resolution if SHOW-SECONDS is non-nil. If RELATIVE is -non-nil, the amount returned will be relative to past time worked." - (interactive) - (let ((string (timeclock-seconds-to-string (timeclock-workday-elapsed) - show-seconds))) - (if (interactive-p) - (message string) - string))) - -(defsubst timeclock-time-to-seconds (time) - "Convert TIME to a floating point number." - (+ (* (car time) 65536.0) - (cadr time) - (/ (or (car (cdr (cdr time))) 0) 1000000.0))) - -(defsubst timeclock-seconds-to-time (seconds) - "Convert SECONDS (a floating point number) to an Emacs time structure." - (list (floor seconds 65536) - (floor (mod seconds 65536)) - (floor (* (- seconds (ffloor seconds)) 1000000)))) - -;; Should today-only be removed in favour of timeclock-relative? - gm -(defsubst timeclock-when-to-leave (&optional today-only) - "Return a time value representing the end of today's workday. -If TODAY-ONLY is non-nil, the value returned will be relative only to -the time worked today, and not to past time." - (timeclock-seconds-to-time - (- (timeclock-time-to-seconds (current-time)) - (let ((discrep (timeclock-find-discrep))) - (if discrep - (if today-only - (cadr discrep) - (car discrep)) - 0.0))))) - -;;;###autoload -(defun timeclock-when-to-leave-string (&optional show-seconds - today-only) - "Return a string representing the end of today's workday. -This string is relative to the value of `timeclock-workday'. If -SHOW-SECONDS is non-nil, the value printed/returned will include -seconds. If TODAY-ONLY is non-nil, the value returned will be -relative only to the time worked today, and not to past time." - ;; Should today-only be removed in favour of timeclock-relative? - gm - (interactive) - (let* ((then (timeclock-when-to-leave today-only)) - (string - (if show-seconds - (format-time-string "%-I:%M:%S %p" then) - (format-time-string "%-I:%M %p" then)))) - (if (interactive-p) - (message string) - string))) - -;;; Internal Functions: - -(defvar timeclock-project-list nil) -(defvar timeclock-last-project nil) - -(defun timeclock-completing-read (prompt alist &optional default) - "A version of `completing-read' that works on both Emacs and XEmacs." - (if (featurep 'xemacs) - (let ((str (completing-read prompt alist))) - (if (or (null str) (= (length str) 0)) - default - str)) - (completing-read prompt alist nil nil nil nil default))) - -(defun timeclock-ask-for-project () - "Ask the user for the project they are clocking into." - (timeclock-completing-read - (format "Clock into which project (default \"%s\"): " - (or timeclock-last-project - (car timeclock-project-list))) - (mapcar 'list timeclock-project-list) - (or timeclock-last-project - (car timeclock-project-list)))) - -(defvar timeclock-reason-list nil) - -(defun timeclock-ask-for-reason () - "Ask the user for the reason they are clocking out." - (timeclock-completing-read "Reason for clocking out: " - (mapcar 'list timeclock-reason-list))) - -(defun timeclock-update-modeline () - "Update the `timeclock-mode-string' displayed in the modeline. -The value of `timeclock-relative' affects the display as described in -that variable's documentation." - (interactive) - (let ((remainder (timeclock-workday-remaining (not timeclock-relative))) - (last-in (equal (car timeclock-last-event) "i"))) - (when (and (< remainder 0) - (not (and timeclock-day-over - (equal timeclock-day-over - (timeclock-time-to-date - (current-time)))))) - (setq timeclock-day-over - (timeclock-time-to-date (current-time))) - (run-hooks 'timeclock-day-over-hook)) - (setq timeclock-mode-string - (propertize - (format " %c%s%c " - (if last-in ?< ?[) - (timeclock-seconds-to-string remainder nil t) - (if last-in ?> ?])) - 'help-echo "timeclock: time remaining")))) - -(put 'timeclock-mode-string 'risky-local-variable t) - -(defun timeclock-log (code &optional project) - "Log the event CODE to the timeclock log, at the time of call. -If PROJECT is a string, it represents the project which the event is -being logged for. Normally only \"in\" events specify a project." - (with-current-buffer (find-file-noselect timeclock-file) - (goto-char (point-max)) - (if (not (bolp)) - (insert "\n")) - (let ((now (current-time))) - (insert code " " - (format-time-string "%Y/%m/%d %H:%M:%S" now) - (or (and project - (stringp project) - (> (length project) 0) - (concat " " project)) - "") - "\n") - (if (equal (downcase code) "o") - (setq timeclock-last-period - (- (timeclock-time-to-seconds now) - (timeclock-time-to-seconds - (cadr timeclock-last-event))) - timeclock-discrepancy - (+ timeclock-discrepancy - timeclock-last-period))) - (setq timeclock-last-event (list code now project))) - (save-buffer) - (run-hooks 'timeclock-event-hook) - (kill-buffer (current-buffer)))) - -(defvar timeclock-moment-regexp - (concat "\\([bhioO]\\)\\s-+" - "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)\\s-+" - "\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)[ \t]*" "\\([^\n]*\\)")) - -(defsubst timeclock-read-moment () - "Read the moment under point from the timelog." - (if (looking-at timeclock-moment-regexp) - (let ((code (match-string 1)) - (year (string-to-number (match-string 2))) - (mon (string-to-number (match-string 3))) - (mday (string-to-number (match-string 4))) - (hour (string-to-number (match-string 5))) - (min (string-to-number (match-string 6))) - (sec (string-to-number (match-string 7))) - (project (match-string 8))) - (list code (encode-time sec min hour mday mon year) project)))) - -(defun timeclock-last-period (&optional moment) - "Return the value of the last event period. -If the last event was a clock-in, the period will be open ended, and -growing every second. Otherwise, it is a fixed amount which has been -recorded to disk. If MOMENT is non-nil, use that as the current time. -This is only provided for coherency when used by -`timeclock-discrepancy'." - (if (equal (car timeclock-last-event) "i") - (- (timeclock-time-to-seconds (or moment (current-time))) - (timeclock-time-to-seconds - (cadr timeclock-last-event))) - timeclock-last-period)) - -(defsubst timeclock-entry-length (entry) - (- (timeclock-time-to-seconds (cadr entry)) - (timeclock-time-to-seconds (car entry)))) - -(defsubst timeclock-entry-begin (entry) - (car entry)) - -(defsubst timeclock-entry-end (entry) - (cadr entry)) - -(defsubst timeclock-entry-project (entry) - (nth 2 entry)) - -(defsubst timeclock-entry-comment (entry) - (nth 3 entry)) - - -(defsubst timeclock-entry-list-length (entry-list) - (let ((length 0)) - (while entry-list - (setq length (+ length (timeclock-entry-length (car entry-list)))) - (setq entry-list (cdr entry-list))) - length)) - -(defsubst timeclock-entry-list-begin (entry-list) - (timeclock-entry-begin (car entry-list))) - -(defsubst timeclock-entry-list-end (entry-list) - (timeclock-entry-end (car (last entry-list)))) - -(defsubst timeclock-entry-list-span (entry-list) - (- (timeclock-time-to-seconds (timeclock-entry-list-end entry-list)) - (timeclock-time-to-seconds (timeclock-entry-list-begin entry-list)))) - -(defsubst timeclock-entry-list-break (entry-list) - (- (timeclock-entry-list-span entry-list) - (timeclock-entry-list-length entry-list))) - -(defsubst timeclock-entry-list-projects (entry-list) - (let (projects) - (while entry-list - (let ((project (timeclock-entry-project (car entry-list)))) - (if projects - (add-to-list 'projects project) - (setq projects (list project)))) - (setq entry-list (cdr entry-list))) - projects)) - - -(defsubst timeclock-day-required (day) - (or (car day) timeclock-workday)) - -(defsubst timeclock-day-length (day) - (timeclock-entry-list-length (cdr day))) - -(defsubst timeclock-day-debt (day) - (- (timeclock-day-required day) - (timeclock-day-length day))) - -(defsubst timeclock-day-begin (day) - (timeclock-entry-list-begin (cdr day))) - -(defsubst timeclock-day-end (day) - (timeclock-entry-list-end (cdr day))) - -(defsubst timeclock-day-span (day) - (timeclock-entry-list-span (cdr day))) - -(defsubst timeclock-day-break (day) - (timeclock-entry-list-break (cdr day))) - -(defsubst timeclock-day-projects (day) - (timeclock-entry-list-projects (cdr day))) - -(defmacro timeclock-day-list-template (func) - `(let ((length 0)) - (while day-list - (setq length (+ length (,(eval func) (car day-list)))) - (setq day-list (cdr day-list))) - length)) - -(defun timeclock-day-list-required (day-list) - (timeclock-day-list-template 'timeclock-day-required)) - -(defun timeclock-day-list-length (day-list) - (timeclock-day-list-template 'timeclock-day-length)) - -(defun timeclock-day-list-debt (day-list) - (timeclock-day-list-template 'timeclock-day-debt)) - -(defsubst timeclock-day-list-begin (day-list) - (timeclock-day-begin (car day-list))) - -(defsubst timeclock-day-list-end (day-list) - (timeclock-day-end (car (last day-list)))) - -(defun timeclock-day-list-span (day-list) - (timeclock-day-list-template 'timeclock-day-span)) - -(defun timeclock-day-list-break (day-list) - (timeclock-day-list-template 'timeclock-day-break)) - -(defun timeclock-day-list-projects (day-list) - (let (projects) - (while day-list - (let ((projs (timeclock-day-projects (car day-list)))) - (while projs - (if projects - (add-to-list 'projects (car projs)) - (setq projects (list (car projs)))) - (setq projs (cdr projs)))) - (setq day-list (cdr day-list))) - projects)) - - -(defsubst timeclock-current-debt (&optional log-data) - (nth 0 (or log-data (timeclock-log-data)))) - -(defsubst timeclock-day-alist (&optional log-data) - (nth 1 (or log-data (timeclock-log-data)))) - -(defun timeclock-day-list (&optional log-data) - (let ((alist (timeclock-day-alist log-data)) - day-list) - (while alist - (setq day-list (cons (cdar alist) day-list) - alist (cdr alist))) - day-list)) - -(defsubst timeclock-project-alist (&optional log-data) - (nth 2 (or log-data (timeclock-log-data)))) - - -(defun timeclock-log-data (&optional recent-only filename) - "Return the contents of the timelog file, in a useful format. -If the optional argument RECENT-ONLY is non-nil, only show the contents -from the last point where the time debt (see below) was set. -If the optional argument FILENAME is non-nil, it is used instead of -the file specified by `timeclock-file.' - -A timelog contains data in the form of a single entry per line. -Each entry has the form: - - CODE YYYY/MM/DD HH:MM:SS [COMMENT] - -CODE is one of: b, h, i, o or O. COMMENT is optional when the code is -i, o or O. The meanings of the codes are: - - b Set the current time balance, or \"time debt\". Useful when - archiving old log data, when a debt must be carried forward. - The COMMENT here is the number of seconds of debt. - - h Set the required working time for the given day. This must - be the first entry for that day. The COMMENT in this case is - the number of hours in this workday. Floating point amounts - are allowed. - - i Clock in. The COMMENT in this case should be the name of the - project worked on. - - o Clock out. COMMENT is unnecessary, but can be used to provide - a description of how the period went, for example. - - O Final clock out. Whatever project was being worked on, it is - now finished. Useful for creating summary reports. - -When this function is called, it will return a data structure with the -following format: - - (DEBT ENTRIES-BY-DAY ENTRIES-BY-PROJECT) - -DEBT is a floating point number representing the number of seconds -\"owed\" before any work was done. For a new file (one without a 'b' -entry), this is always zero. - -The two entries lists have similar formats. They are both alists, -where the CAR is the index, and the CDR is a list of time entries. -For ENTRIES-BY-DAY, the CAR is a textual date string, of the form -YYYY/MM/DD. For ENTRIES-BY-PROJECT, it is the name of the project -worked on, or t for the default project. - -The CDR for ENTRIES-BY-DAY is slightly different than for -ENTRIES-BY-PROJECT. It has the following form: - - (DAY-LENGTH TIME-ENTRIES...) - -For ENTRIES-BY-PROJECT, there is no DAY-LENGTH member. It is simply a -list of TIME-ENTRIES. Note that if DAY-LENGTH is nil, it means -whatever is the default should be used. - -A TIME-ENTRY is a recorded time interval. It has the following format -\(although generally one does not have to manipulate these entries -directly; see below): - - (BEGIN-TIME END-TIME PROJECT [COMMENT] [FINAL-P]) - -Anyway, suffice it to say there are a lot of structures. Typically -the user is expected to manipulate to the day(s) or project(s) that he -or she wants, at which point the following helper functions may be -used: - - timeclock-day-required - timeclock-day-length - timeclock-day-debt - timeclock-day-begin - timeclock-day-end - timeclock-day-span - timeclock-day-break - timeclock-day-projects - - timeclock-day-list-required - timeclock-day-list-length - timeclock-day-list-debt - timeclock-day-list-begin - timeclock-day-list-end - timeclock-day-list-span - timeclock-day-list-break - timeclock-day-list-projects - - timeclock-entry-length - timeclock-entry-begin - timeclock-entry-end - timeclock-entry-project - timeclock-entry-comment - - timeclock-entry-list-length - timeclock-entry-list-begin - timeclock-entry-list-end - timeclock-entry-list-span - timeclock-entry-list-break - timeclock-entry-list-projects - -A few comments should make the use of the above functions obvious: - - `required' is the amount of time that must be spent during a day, or - sequence of days, in order to have no debt. - - `length' is the actual amount of time that was spent. - - `debt' is the difference between required time and length. A - negative debt signifies overtime. - - `begin' is the earliest moment at which work began. - - `end' is the final moment work was done. - - `span' is the difference between begin and end. - - `break' is the difference between span and length. - - `project' is the project that was worked on, and `projects' is a - list of all the projects that were worked on during a given period. - - `comment', where it applies, could mean anything. - -There are a few more functions available, for locating day and entry -lists: - - timeclock-day-alist LOG-DATA - timeclock-project-alist LOG-DATA - timeclock-current-debt LOG-DATA - -See the documentation for the given function if more info is needed." - (let* ((log-data (list 0.0 nil nil)) - (now (current-time)) - (todays-date (timeclock-time-to-date now)) - last-date-limited last-date-seconds last-date - (line 0) last beg day entry event) - (with-temp-buffer - (insert-file-contents (or filename timeclock-file)) - (when recent-only - (goto-char (point-max)) - (unless (re-search-backward "^b\\s-+" nil t) - (goto-char (point-min)))) - (while (or (setq event (timeclock-read-moment)) - (and beg (not last) - (setq last t event (list "o" now)))) - (setq line (1+ line)) - (cond ((equal (car event) "b") - (setcar log-data (string-to-number (nth 2 event)))) - ((equal (car event) "h") - (setq last-date-limited (timeclock-time-to-date (cadr event)) - last-date-seconds (* (string-to-number (nth 2 event)) - 3600.0))) - ((equal (car event) "i") - (if beg - (error "Error in format of timelog file, line %d" line) - (setq beg t)) - (setq entry (list (cadr event) nil - (and (> (length (nth 2 event)) 0) - (nth 2 event)))) - (let ((date (timeclock-time-to-date (cadr event)))) - (if (and last-date - (not (equal date last-date))) - (progn - (setcar (cdr log-data) - (cons (cons last-date day) - (cadr log-data))) - (setq day (list (and last-date-limited - last-date-seconds)))) - (unless day - (setq day (list (and last-date-limited - last-date-seconds))))) - (setq last-date date - last-date-limited nil))) - ((equal (downcase (car event)) "o") - (if (not beg) - (error "Error in format of timelog file, line %d" line) - (setq beg nil)) - (setcar (cdr entry) (cadr event)) - (let ((desc (and (> (length (nth 2 event)) 0) - (nth 2 event)))) - (if desc - (nconc entry (list (nth 2 event)))) - (if (equal (car event) "O") - (nconc entry (if desc - (list t) - (list nil t)))) - (nconc day (list entry)) - (setq desc (nth 2 entry)) - (let ((proj (assoc desc (nth 2 log-data)))) - (if (null proj) - (setcar (cddr log-data) - (cons (cons desc (list entry)) - (car (cddr log-data)))) - (nconc (cdr proj) (list entry))))))) - (forward-line)) - (if day - (setcar (cdr log-data) - (cons (cons last-date day) - (cadr log-data)))) - log-data))) - -(defun timeclock-find-discrep () - "Calculate time discrepancies, in seconds. -The result is a three element list, containing the total time -discrepancy, today's discrepancy, and the time worked today." - ;; This is not implemented in terms of the functions above, because - ;; it's a bit wasteful to read all of that data in, just to throw - ;; away more than 90% of the information afterwards. - ;; - ;; If it were implemented using those functions, it would look - ;; something like this: - ;; (let ((days (timeclock-day-alist (timeclock-log-data))) - ;; (total 0.0)) - ;; (while days - ;; (setq total (+ total (- (timeclock-day-length (cdar days)) - ;; (timeclock-day-required (cdar days)))) - ;; days (cdr days))) - ;; total) - (let* ((now (current-time)) - (todays-date (timeclock-time-to-date now)) - (first t) (accum 0) (elapsed 0) - event beg last-date avg - last-date-limited last-date-seconds) - (unless timeclock-discrepancy - (when (file-readable-p timeclock-file) - (setq timeclock-project-list nil - timeclock-last-project nil - timeclock-reason-list nil - timeclock-elapsed 0) - (with-temp-buffer - (insert-file-contents timeclock-file) - (goto-char (point-max)) - (unless (re-search-backward "^b\\s-+" nil t) - (goto-char (point-min))) - (while (setq event (timeclock-read-moment)) - (cond ((equal (car event) "b") - (setq accum (string-to-number (nth 2 event)))) - ((equal (car event) "h") - (setq last-date-limited - (timeclock-time-to-date (cadr event)) - last-date-seconds - (* (string-to-number (nth 2 event)) 3600.0))) - ((equal (car event) "i") - (when (and (nth 2 event) - (> (length (nth 2 event)) 0)) - (add-to-list 'timeclock-project-list (nth 2 event)) - (setq timeclock-last-project (nth 2 event))) - (let ((date (timeclock-time-to-date (cadr event)))) - (if (if last-date - (not (equal date last-date)) - first) - (setq first nil - accum (- accum (if last-date-limited - last-date-seconds - timeclock-workday)))) - (setq last-date date - last-date-limited nil) - (if beg - (error "Error in format of timelog file!") - (setq beg (timeclock-time-to-seconds (cadr event)))))) - ((equal (downcase (car event)) "o") - (if (and (nth 2 event) - (> (length (nth 2 event)) 0)) - (add-to-list 'timeclock-reason-list (nth 2 event))) - (if (not beg) - (error "Error in format of timelog file!") - (setq timeclock-last-period - (- (timeclock-time-to-seconds (cadr event)) beg) - accum (+ timeclock-last-period accum) - beg nil)) - (if (equal last-date todays-date) - (setq timeclock-elapsed - (+ timeclock-last-period timeclock-elapsed))))) - (setq timeclock-last-event event - timeclock-last-event-workday - (if (equal (timeclock-time-to-date now) last-date-limited) - last-date-seconds - timeclock-workday)) - (forward-line)) - (setq timeclock-discrepancy accum)))) - (unless timeclock-last-event-workday - (setq timeclock-last-event-workday timeclock-workday)) - (setq accum (or timeclock-discrepancy 0) - elapsed (or timeclock-elapsed elapsed)) - (if timeclock-last-event - (if (equal (car timeclock-last-event) "i") - (let ((last-period (timeclock-last-period now))) - (setq accum (+ accum last-period) - elapsed (+ elapsed last-period))) - (if (not (equal (timeclock-time-to-date - (cadr timeclock-last-event)) - (timeclock-time-to-date now))) - (setq accum (- accum timeclock-last-event-workday))))) - (list accum (- elapsed timeclock-last-event-workday) - elapsed))) - -;;; A reporting function that uses timeclock-log-data - -(defun timeclock-day-base (&optional time) - "Given a time within a day, return 0:0:0 within that day. -If optional argument TIME is non-nil, use that instead of the current time." - (let ((decoded (decode-time (or time (current-time))))) - (setcar (nthcdr 0 decoded) 0) - (setcar (nthcdr 1 decoded) 0) - (setcar (nthcdr 2 decoded) 0) - (apply 'encode-time decoded))) - -(defun timeclock-geometric-mean (l) - "Compute the geometric mean of the values in the list L." - (let ((total 0) - (count 0)) - (while l - (setq total (+ total (car l)) - count (1+ count) - l (cdr l))) - (if (> count 0) - (/ total count) - 0))) - -(defun timeclock-generate-report (&optional html-p) - "Generate a summary report based on the current timelog file. -By default, the report is in plain text, but if the optional argument -HTML-P is non-nil, HTML markup is added." - (interactive) - (let ((log (timeclock-log-data)) - (today (timeclock-day-base))) - (if html-p (insert "

")) - (insert "Currently ") - (let ((project (nth 2 timeclock-last-event)) - (begin (nth 1 timeclock-last-event)) - done) - (if (timeclock-currently-in-p) - (insert "IN") - (if (or (null project) (= (length project) 0)) - (progn (insert "Done Working Today") - (setq done t)) - (insert "OUT"))) - (unless done - (insert " since " (format-time-string "%Y/%m/%d %-I:%M %p" begin)) - (if html-p - (insert "
\n") - (insert "\n*")) - (if (timeclock-currently-in-p) - (insert "Working on ")) - (if html-p - (insert project "
\n") - (insert project "*\n")) - (let ((proj-data (cdr (assoc project (timeclock-project-alist log)))) - (two-weeks-ago (timeclock-seconds-to-time - (- (timeclock-time-to-seconds today) - (* 2 7 24 60 60)))) - two-week-len today-len) - (while proj-data - (if (not (time-less-p - (timeclock-entry-begin (car proj-data)) today)) - (setq today-len (timeclock-entry-list-length proj-data) - proj-data nil) - (if (and (null two-week-len) - (not (time-less-p - (timeclock-entry-begin (car proj-data)) - two-weeks-ago))) - (setq two-week-len (timeclock-entry-list-length proj-data))) - (setq proj-data (cdr proj-data)))) - (if (null two-week-len) - (setq two-week-len today-len)) - (if html-p (insert "

")) - (if today-len - (insert "\nTime spent on this task today: " - (timeclock-seconds-to-string today-len) - ". In the last two weeks: " - (timeclock-seconds-to-string two-week-len)) - (if two-week-len - (insert "\nTime spent on this task in the last two weeks: " - (timeclock-seconds-to-string two-week-len)))) - (if html-p (insert "
")) - (insert "\n" - (timeclock-seconds-to-string (timeclock-workday-elapsed)) - " worked today, " - (timeclock-seconds-to-string (timeclock-workday-remaining)) - " remaining, done at " - (timeclock-when-to-leave-string) "\n"))) - (if html-p (insert "

")) - (insert "\nThere have been " - (number-to-string - (length (timeclock-day-alist log))) - " days of activity, starting " - (caar (last (timeclock-day-alist log)))) - (if html-p (insert "

")) - (when html-p - (insert "

- -

- - - - - - - -") - (let* ((day-list (timeclock-day-list)) - (thirty-days-ago (timeclock-seconds-to-time - (- (timeclock-time-to-seconds today) - (* 30 24 60 60)))) - (three-months-ago (timeclock-seconds-to-time - (- (timeclock-time-to-seconds today) - (* 90 24 60 60)))) - (six-months-ago (timeclock-seconds-to-time - (- (timeclock-time-to-seconds today) - (* 180 24 60 60)))) - (one-year-ago (timeclock-seconds-to-time - (- (timeclock-time-to-seconds today) - (* 365 24 60 60)))) - (time-in (vector (list t) (list t) (list t) (list t) (list t))) - (time-out (vector (list t) (list t) (list t) (list t) (list t))) - (breaks (vector (list t) (list t) (list t) (list t) (list t))) - (workday (vector (list t) (list t) (list t) (list t) (list t))) - (lengths (vector '(0 0) thirty-days-ago three-months-ago - six-months-ago one-year-ago))) - ;; collect statistics from complete timelog - (while day-list - (let ((i 0) (l 5)) - (while (< i l) - (unless (time-less-p - (timeclock-day-begin (car day-list)) - (aref lengths i)) - (let ((base (timeclock-time-to-seconds - (timeclock-day-base - (timeclock-day-begin (car day-list)))))) - (nconc (aref time-in i) - (list (- (timeclock-time-to-seconds - (timeclock-day-begin (car day-list))) - base))) - (let ((span (timeclock-day-span (car day-list))) - (len (timeclock-day-length (car day-list))) - (req (timeclock-day-required (car day-list)))) - ;; If the day's actual work length is less than - ;; 70% of its span, then likely the exit time - ;; and break amount are not worthwhile adding to - ;; the statistic - (when (and (> span 0) - (> (/ (float len) (float span)) 0.70)) - (nconc (aref time-out i) - (list (- (timeclock-time-to-seconds - (timeclock-day-end (car day-list))) - base))) - (nconc (aref breaks i) (list (- span len)))) - (if req - (setq len (+ len (- timeclock-workday req)))) - (nconc (aref workday i) (list len))))) - (setq i (1+ i)))) - (setq day-list (cdr day-list))) - ;; average statistics - (let ((i 0) (l 5)) - (while (< i l) - (aset time-in i (timeclock-geometric-mean - (cdr (aref time-in i)))) - (aset time-out i (timeclock-geometric-mean - (cdr (aref time-out i)))) - (aset breaks i (timeclock-geometric-mean - (cdr (aref breaks i)))) - (aset workday i (timeclock-geometric-mean - (cdr (aref workday i)))) - (setq i (1+ i)))) - ;; Output the HTML table - (insert "\n") - (insert "\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "\n") - (setq i (1+ i)))) - (insert "\n") - - (insert "\n") - (insert "\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "\n") - (setq i (1+ i)))) - (insert "\n") - - (insert "\n") - (insert "\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "\n") - (setq i (1+ i)))) - (insert "\n") - - (insert "\n") - (insert "\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "\n") - (setq i (1+ i)))) - (insert "\n")) - (insert " - - -
StatisticsEntire-30 days-3 mons-6 mons-1 year
Time in" - (timeclock-seconds-to-string (aref time-in i)) - "
Time out" - (timeclock-seconds-to-string (aref time-out i)) - "
Break" - (timeclock-seconds-to-string (aref breaks i)) - "
Workday" - (timeclock-seconds-to-string (aref workday i)) - "
- These are approximate figures
-
"))))) - -;;; A helpful little function - -(defun timeclock-visit-timelog () - "Open the file named by `timeclock-file' in another window." - (interactive) - (find-file-other-window timeclock-file)) - -(provide 'timeclock) - -(run-hooks 'timeclock-load-hook) - -;; make sure we know the list of reasons, projects, and have computed -;; the last event and current discrepancy. -(if (file-readable-p timeclock-file) - (timeclock-reread-log)) - -;;; arch-tag: a0be3377-deb6-44ec-b9a2-a7be28436a40 -;;; timeclock.el ends here diff --git a/times.cc b/times.cc deleted file mode 100644 index 8966d598..00000000 --- a/times.cc +++ /dev/null @@ -1,53 +0,0 @@ -#include "times.h" - -namespace ledger { - -#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(); - -#ifdef SUPPORT_DATE_AND_TIME -const moment_t& now(time_now); -#else -const moment_t& now(date_now); -#endif - -bool day_before_month = false; -static bool day_before_month_initialized = false; - -moment_t parse_datetime(const char * str) -{ - if (! day_before_month_initialized) { -#ifdef HAVE_NL_LANGINFO - const char * d_fmt = nl_langinfo(D_FMT); - if (d_fmt && std::strlen(d_fmt) > 1 && d_fmt[1] == 'd') - day_before_month = true; - day_before_month_initialized = true; -#endif - } -#if 0 - return parse_abs_datetime(in); -#else - int year = ((str[0] - '0') * 1000 + - (str[1] - '0') * 100 + - (str[2] - '0') * 10 + - (str[3] - '0')); - - int mon = ((str[5] - '0') * 10 + - (str[6] - '0')); - - int day = ((str[8] - '0') * 10 + - (str[9] - '0')); - - return moment_t(boost::gregorian::date(year, mon, day)); -#endif -} - -moment_t datetime_range_from_stream(std::istream& in) -{ -} - -} // namespace ledger diff --git a/times.h b/times.h deleted file mode 100644 index 2cc0d7e4..00000000 --- a/times.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef _TIMES_H -#define _TIMES_H - -#include "utils.h" - -#include - -namespace ledger { - -typedef boost::posix_time::ptime ptime; -typedef ptime::time_duration_type time_duration; -typedef boost::gregorian::date date; -typedef boost::gregorian::date_duration date_duration; -typedef boost::posix_time::seconds seconds; - -#define SUPPORT_DATE_AND_TIME 1 -#ifdef SUPPORT_DATE_AND_TIME - -typedef boost::posix_time::ptime moment_t; -typedef moment_t::time_duration_type duration_t; - -inline bool is_valid_moment(const moment_t& moment) { - return ! moment.is_not_a_date_time(); -} - -#else // SUPPORT_DATE_AND_TIME - -typedef boost::gregorian::date moment_t; -typedef boost::gregorian::date_duration duration_t; - -inline bool is_valid_moment(const moment_t& moment) { - return ! moment.is_not_a_date(); -} - -#endif // SUPPORT_DATE_AND_TIME - -extern const moment_t& now; - -DECLARE_EXCEPTION(datetime_exception); - -class interval_t -{ -public: - interval_t() {} - interval_t(const string&) {} - - operator bool() const { - return false; - } - - void start(const moment_t&) {} - moment_t next() const { return moment_t(); } - - void parse(std::istream&) {} -}; - -#if 0 -inline moment_t ptime_local_to_utc(const moment_t& when) { - struct std::tm tm_gmt = to_tm(when); - return boost::posix_time::from_time_t(std::mktime(&tm_gmt)); -} - -// jww (2007-04-18): I need to make a general parsing function -// instead, and then make these into private methods. -inline moment_t ptime_from_local_date_string(const string& date_string) { - return ptime_local_to_utc(moment_t(boost::gregorian::from_string(date_string), - time_duration())); -} - -inline moment_t ptime_from_local_time_string(const string& time_string) { - return ptime_local_to_utc(boost::posix_time::time_from_string(time_string)); -} -#endif - -moment_t parse_datetime(const char * str); - -inline moment_t parse_datetime(const string& str) { - return parse_datetime(str.c_str()); -} - -extern const ptime time_now; -extern const date date_now; -extern bool day_before_month; - -#if 0 -struct intorchar -{ - int ival; - string sval; - - intorchar() : ival(-1) {} - intorchar(int val) : ival(val) {} - intorchar(const string& val) : ival(-1), sval(val) {} - intorchar(const intorchar& o) : ival(o.ival), sval(o.sval) {} -}; - -ledger::moment_t parse_abs_datetime(std::istream& input); -#endif - -} // namespace ledger - -#endif // _TIMES_H diff --git a/transform.cc b/transform.cc deleted file mode 100644 index b6a25cee..00000000 --- a/transform.cc +++ /dev/null @@ -1,326 +0,0 @@ -#include "transform.h" - -namespace ledger { - -#if 0 -void populate_account(account_t& acct, xml::document_t * document) -{ - if (! acct.parent) - return; - - account_repitem_t * acct_item; - if (acct.data == NULL) { - acct.data = acct_item = - static_cast(repitem_t::wrap(&acct)); - if (acct.parent) { - if (acct.parent->data == NULL) - populate_account(*acct.parent, acct_item); - else - static_cast(acct.parent->data)-> - add_child(acct_item); - } - } else { - acct_item = static_cast(acct.data); - } - - if (item->kind == repitem_t::ACCOUNT) - acct_item->add_child(item); - else - acct_item->add_content(item); -} - -class populate_accounts : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - if (item->kind == repitem_t::TRANSACTION) { - item->extract(); - populate_account(*static_cast(item)->account(), item); - } - } -}; - -class clear_account_data : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - if (item->kind == repitem_t::ACCOUNT) - static_cast(item)->account->data = NULL; - } -}; - -void accounts_transform::execute(xml::document_t * document) -{ - populate_accounts cb1; - items->select_all(cb1); - - for (repitem_t * j = items->children; j; j = j->next) { - assert(j->kind == repitem_t::JOURNAL); - - j->clear(); - - for (accounts_map::iterator i = j->journal->master->accounts.begin(); - i != j->journal->master->accounts.end(); - i++) { - assert((*i).second->data); - j->add_child(static_cast((*i).second->data)); - (*i).second->data = NULL; - } - } - - clear_account_data cb2; - items->select_all(cb2); -} - -void compact_transform::execute(xml::document_t * document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->kind == repitem_t::ACCOUNT) { - while (! i->contents && - i->children && ! i->children->next) { - account_repitem_t * p = static_cast(i); - i = p->children; - p->children = NULL; - p->last_child = NULL; - - i->set_parent(p->parent); - p->set_parent(NULL); - i->prev = p->prev; - if (p->prev) - p->prev->next = i; - p->prev = NULL; - i->next = p->next; - if (p->next) - p->next->prev = i; - p->next = NULL; - - if (i->parent->children == p) - i->parent->children = i; - if (i->parent->last_child == p) - i->parent->last_child = i; - - account_repitem_t * acct = static_cast(i); - acct->parents_elided = p->parents_elided + 1; - - delete p; - } - } - - if (i->children) - execute(i->children); - } -} - -void clean_transform::execute(xml::document_t * document) -{ - repitem_t * i = items; - while (i) { - if (i->kind == repitem_t::ACCOUNT) { - value_t temp; - i->add_total(temp); - if (! temp) { - repitem_t * next = i->next; - delete i; - i = next; - continue; - } - } -#if 0 - else if (i->kind == repitem_t::ENTRY && ! i->contents) { - assert(! i->children); - repitem_t * next = i->next; - delete i; - i = next; - continue; - } -#endif - - if (i->children) - execute(i->children); - - i = i->next; - } -} - -void entries_transform::execute(xml::document_t * document) -{ -} - -void optimize_transform::execute(xml::document_t * document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->kind == repitem_t::ENTRY) { - if (i->contents && - i->contents->next && - ! i->contents->next->next) { // exactly two transactions - xact_repitem_t * first = - static_cast(i->contents); - xact_repitem_t * second = - static_cast(i->contents->next); - if (first->xact->amount == - second->xact->amount) - ; - } - } - - if (i->children) - execute(i->children); - } -} - -void split_transform::execute(xml::document_t * document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->contents && i->contents->next) { - repitem_t * j; - - switch (i->kind) { - case repitem_t::TRANSACTION: - assert(0); - j = new xact_repitem_t(static_cast(i)->xact); - break; - case repitem_t::ENTRY: - j = new entry_repitem_t(static_cast(i)->entry); - break; - case repitem_t::ACCOUNT: - j = new account_repitem_t(static_cast(i)->account); - break; - default: - j = new repitem_t(i->kind); - break; - } - - j->set_parent(i->parent); - j->prev = i; - j->next = i->next; - i->next = j; - - j->contents = i->contents->next; - j->contents->prev = NULL; - j->contents->set_parent(j); - i->contents->next = NULL; - - j->last_content = i->last_content; - if (j->contents == i->last_content) - i->last_content = i->contents; - } - - if (i->children) - execute(i->children); - } -} - -void merge_transform::execute(xml::document_t * document) -{ - for (repitem_t * i = items; i; i = i->next) { - if (i->next) { - assert(i->kind == i->next->kind); - bool merge = false; - switch (i->kind) { - case repitem_t::TRANSACTION: - assert(0); - break; - case repitem_t::ENTRY: - if (static_cast(i)->entry == - static_cast(i->next)->entry) - merge = true; - break; - case repitem_t::ACCOUNT: -#if 0 - if (static_cast(i)->account == - static_cast(i->next)->account) - merge = true; -#endif - break; - default: - break; - } - - if (merge) { - repitem_t * j = i->next; - - i->next = i->next->next; - if (i->next) - i->next->prev = i; - - for (repitem_t * k = j->contents; k; k = k->next) - k->set_parent(i); - - i->last_content->next = j->contents; - i->last_content = j->last_content; - - j->contents = NULL; - assert(! j->children); - delete j; - } - } - - if (i->children) - execute(i->children); - } -} - -namespace { -#define REPITEM_FLAGGED 0x1 - - class mark_selected : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - item->flags |= REPITEM_FLAGGED; - } - }; - - class mark_selected_and_ancestors : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - while (item->parent) { - item->flags |= REPITEM_FLAGGED; - item = item->parent; - } - } - }; - - class delete_unmarked : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - if (item->parent && ! (item->flags & REPITEM_FLAGGED)) - delete item; - } - }; - - class delete_marked : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - if (item->flags & REPITEM_FLAGGED) - delete item; - } - }; - - class clear_flags : public repitem_t::select_callback_t { - virtual void operator()(xml::document_t * document) { - item->flags = 0; - } - }; -} - -void select_transform::execute(xml::document_t * document) -{ - if (! path) { - items->clear(); - return; - } - mark_selected_and_ancestors cb1; - items->select(path, cb1); - - delete_unmarked cb2; - items->select_all(cb2); - clear_flags cb3; - items->select_all(cb3); -} - -void remove_transform::execute(xml::document_t * document) -{ - if (! path) - return; - mark_selected cb1; - items->select(path, cb1); - - delete_marked cb2; - items->select_all(cb2); - clear_flags cb3; - items->select_all(cb3); -} -#endif - -} // namespace ledger diff --git a/transform.h b/transform.h deleted file mode 100644 index 1a5286a1..00000000 --- a/transform.h +++ /dev/null @@ -1,136 +0,0 @@ -#ifndef _TRANSFORM_H -#define _TRANSFORM_H - -#include "xpath.h" - -namespace ledger { - -class transform_t { - public: - virtual ~transform_t() {} - virtual void execute(xml::document_t * document) = 0; -}; - -class check_transform : public transform_t { - // --check checks the validity of the item list. - public: - virtual void execute(xml::document_t * document); -}; - -class accounts_transform : public transform_t { - // --accounts transforms the report tree into an account-wise view. - public: - virtual void execute(xml::document_t * document); -}; - -class compact_transform : public transform_t { - // --compact compacts an account tree to remove accounts with only - // one child account. - public: - virtual void execute(xml::document_t * document); -}; - -class clean_transform : public transform_t { - // --clean clears out entries and accounts that have no contents. - public: - virtual void execute(xml::document_t * document); -}; - -class entries_transform : public transform_t { - // --entries transforms the report tree into an entries-wise view. - public: - virtual void execute(xml::document_t * document); -}; - -class optimize_transform : public transform_t { - // --optimize optimizes entries for display by the print command. - // What this means is that if an entry has two transactions of the - // commodity (one the negative of the other), the amount of the - // second transaction will be nulled out. - public: - virtual void execute(xml::document_t * document); -}; - -class split_transform : public transform_t { - // --split breaks entry with two or more transactions into what - // seems like two entries each with one transaction -- even though - // it is the same entry being reported in both cases. This is - // useful before sorting, for exampel, in order to sort by - // transaction instead of by entry. - public: - virtual void execute(xml::document_t * document); -}; - -class merge_transform : public transform_t { - // --merge is the opposite of --split: any adjacent transactions - // which share the same entry will be merged into a group of - // transactions under one reported entry. - public: - virtual void execute(xml::document_t * document); -}; - -class combine_transform : public transform_t { - // --combine EXPR combines all transactions matching EXPR so that - // they appear within the same virtual entry (whose date will span - // the earliest to the latest of those entries, and whose payee name - // will show the terminating date or a label that is characteristic - // of the set). - public: - virtual void execute(xml::document_t * document); -}; - -class group_transform : public transform_t { - // --group groups all transactions that affect the same account - // within an entry, so that they appear as a single transaction. - public: - virtual void execute(xml::document_t * document); -}; - -class collapse_transform : public transform_t { - // --collapse makes all transactions within an entry appear as a - // single transaction, even if they affect different accounts. The - // fictitous account "" is used to represent the final sum, - // if multiple accounts are involved. - public: - virtual void execute(xml::document_t * document); -}; - -class subtotal_transform : public transform_t { - // --subtotal will combine the transactions from all entries into - // one giant entry. When used in conjunction with --group, the - // affect is very similar to a regular balance report. - public: - virtual void execute(xml::document_t * document); -}; - -#if 0 -class select_transform : public transform_t -{ - protected: - xml::xpath_t xpath; - - public: - select_transform(const string& selection_path) { - xpath.parse(selection_path); - } - virtual ~select_transform() { - if (path) - delete path; - } - - virtual void execute(xml::document_t * document); -}; - -class remove_transform : public select_transform -{ - public: - remove_transform(const string& selection_path) - : select_transform(selection_path) {} - - virtual void execute(xml::document_t * document); -}; -#endif - -} // namespace ledger - -#endif // _TRANSFORM_H diff --git a/utils.cc b/utils.cc deleted file mode 100644 index 63c28c5c..00000000 --- a/utils.cc +++ /dev/null @@ -1,687 +0,0 @@ -#include "utils.h" -#include "times.h" - -/********************************************************************** - * - * Assertions - */ - -#if defined(ASSERTS_ON) - -namespace ledger { - -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 - << ": " << reason; - throw exception(buf.str(), context()); -} - -} // namespace ledger - -#endif - -/********************************************************************** - * - * Verification (basically, very slow asserts) - */ - -#if defined(VERIFY_ON) - -namespace ledger { - -bool verify_enabled = false; - -typedef std::pair allocation_pair; -typedef std::map live_memory_map; -typedef std::pair live_memory_pair; -typedef std::multimap live_objects_map; -typedef std::pair live_objects_pair; -typedef std::pair count_size_pair; -typedef std::map object_count_map; -typedef std::pair object_count_pair; - -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); - } - - delete live_memory; live_memory = NULL; - delete live_memory_count; live_memory_count = NULL; - delete total_memory_count; total_memory_count = NULL; - - delete live_objects; live_objects = NULL; - delete live_object_count; live_object_count = NULL; - delete total_object_count; total_object_count = NULL; - 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 result = - the_map.insert(object_count_pair(name, count_size_pair(1, size))); - VERIFY(result.second); - } -} - -std::size_t current_memory_size() -{ - std::size_t memory_size = 0; - - for (object_count_map::const_iterator i = live_memory_count->begin(); - i != live_memory_count->end(); - i++) - memory_size += (*i).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_pair(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) -{ - for (object_count_map::iterator i = the_map.begin(); - i != the_map.end(); - i++) - out << " " << std::right << std::setw(12) << (*i).second.first - << " " << std::right << std::setw(12) << (*i).second.second - << " " << std::left << (*i).first - << std::endl; -} - -std::size_t current_objects_size() -{ - std::size_t objects_size = 0; - - for (object_count_map::const_iterator i = live_object_count->begin(); - i != live_object_count->end(); - i++) - objects_size += (*i).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_("verify.memory", "TRACE_CTOR " << ptr << " " << name); - - live_objects->insert(live_objects_pair(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_("ledger.trace.debug", "TRACE_DTOR " << ptr << " " << cls_name); - - live_objects_map::iterator i = live_objects->find(ptr); - VERIFY(i != live_objects->end()); - - 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; - - for (live_memory_map::const_iterator i = live_memory->begin(); - i != live_memory->end(); - i++) - out << " " << std::right << std::setw(7) << (*i).first - << " " << std::right << std::setw(7) << (*i).second.second - << " " << std::left << (*i).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; - - for (live_objects_map::const_iterator i = live_objects->begin(); - i != live_objects->end(); - i++) - out << " " << std::right << std::setw(7) << (*i).first - << " " << std::right << std::setw(7) << (*i).second.second - << " " << std::left << (*i).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); - } - } -} - -#if ! defined(USE_BOOST_PYTHON) - -string::string() : std::string() { - TRACE_CTOR(string, ""); -} -string::string(const string& str) : std::string(str) { - TRACE_CTOR(string, "const string&"); -} -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() { - TRACE_DTOR(string); -} - -#endif - -} // namespace ledger - -#endif // VERIFY_ON - -/********************************************************************** - * - * Logging - */ - -#if defined(LOGGING_ON) - -namespace ledger { - -log_level_t _log_level; -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 if (size < (1024 * 1024 * 1024 * 1024)) - out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G'; - else - assert(false); -} - -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 - now).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) - -#include - -namespace ledger { - -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 timer_map; -typedef std::pair timer_pair; - -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_pair(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 _exc_buffer; - -} // namespace ledger - -/********************************************************************** - * - * General utility functions - */ - -namespace ledger { - -string expand_path(const string& path) -{ - if (path.length() == 0 || path[0] != '~') - return path; - - const char * pfx = NULL; - 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 { - string user(path, 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 path; - - string result(pfx); - - if (pos == string::npos) - return result; - - if (result.length() == 0 || result[result.length() - 1] != '/') - result += '/'; - - result += path.substr(pos + 1); - - return result; -} - -string resolve_path(const string& path) -{ - if (path[0] == '~') - return expand_path(path); - return path; -} - -} // namespace ledger diff --git a/utils.h b/utils.h deleted file mode 100644 index 79595c71..00000000 --- a/utils.h +++ /dev/null @@ -1,422 +0,0 @@ -#ifndef _UTILS_H -#define _UTILS_H - -#include - -/********************************************************************** - * - * Forward declarations - */ - -namespace ledger { -#if ! defined(USE_BOOST_PYTHON) - class string; -#else - typedef std::string string; -#endif -} - -/********************************************************************** - * - * Default values - */ - -#if defined(FULL_DEBUG) -#define VERIFY_ON 1 -#define TRACING_ON 1 -#define DEBUG_ON 1 -#define TIMERS_ON 1 -#define FREE_MEMORY 1 -#elif defined(NO_DEBUG) -#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 // jww (2007-04-25): is this correct? -#endif - -/********************************************************************** - * - * Assertions - */ - -#ifdef assert -#undef assert -#endif - -#if ! defined(NO_ASSERTS) -#define ASSERTS_ON 1 -#endif -#if defined(ASSERTS_ON) - -#include - -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__)) - -#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 -#define IF_VERIFY() if (DO_VERIFY()) - -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() ? trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0)) -#define TRACE_DTOR(cls) \ - (DO_VERIFY() ? trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0)) - -void report_memory(std::ostream& out, bool report_all = false); - -#if ! defined(USE_BOOST_PYTHON) - -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(); -}; - -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; } - -#endif - -} // namespace ledger - -#else // ! VERIFY_ON - -#define VERIFY(x) -#define TRACE_CTOR(cls, args) -#define TRACE_DTOR(cls) - -#endif // VERIFY_ON - -/********************************************************************** - * - * 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) \ - (_log_level >= LOG_TRACE && lvl <= _trace_level) -#define TRACE(lvl, msg) \ - (SHOW_TRACE(lvl) ? ((_log_buffer << msg), logger_func(LOG_TRACE)) : false) - -#else // TRACING_ON - -#define SHOW_TRACE(lvl) false -#define TRACE(lvl, msg) - -#endif // TRACING_ON - -#if defined(DEBUG_ON) - -extern std::string _log_category; - -inline bool category_matches(const char * cat) { - return (_log_category == cat || - (std::strlen(cat) > _log_category.size() + 1 && - std::strncmp(cat, _log_category.c_str(), - _log_category.size()) == 0 && - cat[_log_category.size()] == '.')); -} - -#define SHOW_DEBUG_(cat) \ - (_log_level >= LOG_DEBUG && category_matches(cat)) -#define SHOW_DEBUG() SHOW_DEBUG_(_this_category) - -#define DEBUG_(cat, msg) \ - (SHOW_DEBUG_(cat) ? ((_log_buffer << msg), logger_func(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) \ - (_log_level >= level ? \ - ((_log_buffer << msg), logger_func(level)) : false) - -#define SHOW_INFO() (_log_level >= LOG_INFO) -#define SHOW_WARN() (_log_level >= LOG_WARN) -#define SHOW_ERROR() (_log_level >= LOG_ERROR) -#define SHOW_FATAL() (_log_level >= LOG_FATAL) -#define SHOW_CRITICAL() (_log_level >= LOG_CRIT) - -#define INFO(msg) LOG_MACRO(LOG_INFO, msg) -#define WARN(msg) LOG_MACRO(LOG_WARN, msg) -#define ERROR(msg) LOG_MACRO(LOG_ERROR, msg) -#define FATAL(msg) LOG_MACRO(LOG_FATAL, msg) -#define CRITICAL(msg) LOG_MACRO(LOG_CRIT, msg) -#define EXCEPTION(msg) LOG_MACRO(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(msg) -#define DEBUG_(cat, 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) ? \ - ((_log_buffer << msg), start_timer(#name, LOG_TRACE)) : ((void)0)) -#define TRACE_STOP(name, lvl) \ - (SHOW_TRACE(lvl) ? stop_timer(#name) : ((void)0)) -#define TRACE_FINISH(name, lvl) \ - (SHOW_TRACE(lvl) ? 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) ? \ - ((_log_buffer << msg), start_timer(#name, LOG_DEBUG)) : ((void)0)) -#define DEBUG_START(name, msg) \ - DEBUG_START_(name, _this_category, msg) -#define DEBUG_STOP_(name, cat) \ - (SHOW_DEBUG_(cat) ? stop_timer(#name) : ((void)0)) -#define DEBUG_STOP(name) \ - DEBUG_STOP_(name, _this_category) -#define DEBUG_FINISH_(name, cat) \ - (SHOW_DEBUG_(cat) ? finish_timer(#name) : ((void)0)) -#define DEBUG_FINISH(name) \ - DEBUG_FINISH_(name, _this_category) -#else -#define DEBUG_START(name, msg) -#define DEBUG_START_(name, cat, msg) -#define DEBUG_STOP(name) -#define DEBUG_FINISH(name) -#endif - -#define INFO_START(name, msg) \ - (SHOW_INFO() ? \ - ((_log_buffer << msg), start_timer(#name, 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 - */ - -#include "error.h" - -namespace ledger { - -extern std::ostringstream _exc_buffer; - -template -inline void throw_func(const std::string& message) { - _exc_buffer.str(""); - throw T(message, context()); -} - -#define throw_(cls, msg) \ - ((_exc_buffer << msg), throw_func(_exc_buffer.str())) - -} // namespace ledger - -/********************************************************************** - * - * General utility functions - */ - -namespace ledger { - -string resolve_path(const string& path); - -#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; -} - -} // namespace ledger - -#endif // _UTILS_H diff --git a/value.cc b/value.cc deleted file mode 100644 index 914a49e2..00000000 --- a/value.cc +++ /dev/null @@ -1,2311 +0,0 @@ -#include "value.h" -#include "xml.h" - -namespace ledger { - -bool value_t::to_boolean() const -{ - if (type == BOOLEAN) { - return *(bool *) data; - } else { - value_t temp(*this); - temp.in_place_cast(BOOLEAN); - return *(bool *) temp.data; - } -} - -long value_t::to_integer() const -{ - if (type == INTEGER) { - return *(long *) data; - } else { - value_t temp(*this); - temp.in_place_cast(INTEGER); - return *(long *) temp.data; - } -} - -moment_t value_t::to_datetime() const -{ - if (type == DATETIME) { - return *(moment_t *) data; - } else { - value_t temp(*this); - temp.in_place_cast(DATETIME); - return *(moment_t *) temp.data; - } -} - -amount_t value_t::to_amount() const -{ - if (type == AMOUNT) { - return *(amount_t *) data; - } else { - value_t temp(*this); - temp.in_place_cast(AMOUNT); - return *(amount_t *) temp.data; - } -} - -balance_t value_t::to_balance() const -{ - if (type == BALANCE) { - return *(balance_t *) data; - } else { - value_t temp(*this); - temp.in_place_cast(BALANCE); - return *(balance_t *) temp.data; - } -} - -balance_pair_t value_t::to_balance_pair() const -{ - if (type == BALANCE_PAIR) { - return *(balance_pair_t *) data; - } else { - value_t temp(*this); - temp.in_place_cast(BALANCE_PAIR); - return *(balance_pair_t *) temp.data; - } -} - -string value_t::to_string() const -{ - if (type == STRING) { - return **(string **) data; - } else { - std::ostringstream out; - out << *this; - return out.str(); - } -} - -xml::node_t * value_t::to_xml_node() const -{ - if (type == XML_NODE) - return *(xml::node_t **) data; - else - throw_(value_exception, "Value is not an XML node"); -} - -void * value_t::to_pointer() const -{ - if (type == POINTER) - return *(void **) data; - else - throw_(value_exception, "Value is not a pointer"); -} - -value_t::sequence_t * value_t::to_sequence() const -{ - if (type == SEQUENCE) - return *(sequence_t **) data; - else - throw_(value_exception, "Value is not a sequence"); -} - -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; - case STRING: - delete *(string **) data; - break; - case SEQUENCE: - delete *(sequence_t **) data; - break; - default: - break; - } -} - -void value_t::simplify() -{ - if (realzero()) { - DEBUG_("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_("amounts.values.simplify", "Reducing balance pair to balance"); - in_place_cast(BALANCE); - } - - if (type == BALANCE && - ((balance_t *) data)->amounts.size() == 1) { - DEBUG_("amounts.values.simplify", "Reducing balance to amount"); - in_place_cast(AMOUNT); - } - - if (type == AMOUNT && - ! ((amount_t *) data)->commodity()) { - DEBUG_("amounts.values.simplify", "Reducing amount to integer"); - in_place_cast(INTEGER); - } -} - -value_t& value_t::operator=(const value_t& val) -{ - if (this == &val) - return *this; - - if (type == BOOLEAN && val.type == BOOLEAN) { - *((bool *) data) = *((bool *) val.data); - return *this; - } - else if (type == INTEGER && val.type == INTEGER) { - *((long *) data) = *((long *) val.data); - return *this; - } - else if (type == DATETIME && val.type == DATETIME) { - *((moment_t *) data) = *((moment_t *) val.data); - return *this; - } - else if (type == AMOUNT && val.type == AMOUNT) { - *(amount_t *) data = *(amount_t *) val.data; - return *this; - } - else if (type == BALANCE && val.type == BALANCE) { - *(balance_t *) data = *(balance_t *) val.data; - return *this; - } - else if (type == BALANCE_PAIR && val.type == BALANCE_PAIR) { - *(balance_pair_t *) data = *(balance_pair_t *) val.data; - return *this; - } - else if (type == STRING && val.type == STRING) { - **(string **) data = **(string **) val.data; - return *this; - } - else if (type == SEQUENCE && val.type == SEQUENCE) { - **(sequence_t **) data = **(sequence_t **) val.data; - return *this; - } - - destroy(); - - switch (val.type) { - case BOOLEAN: - *((bool *) data) = *((bool *) val.data); - break; - - case INTEGER: - *((long *) data) = *((long *) val.data); - break; - - case DATETIME: - *((moment_t *) data) = *((moment_t *) val.data); - break; - - case AMOUNT: - new((amount_t *)data) amount_t(*((amount_t *) val.data)); - break; - - case BALANCE: - new((balance_t *)data) balance_t(*((balance_t *) val.data)); - break; - - case BALANCE_PAIR: - new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) val.data)); - break; - - case STRING: - *(string **) data = new string(**(string **) val.data); - break; - - case XML_NODE: - *(xml::node_t **) data = *(xml::node_t **) val.data; - break; - - case POINTER: - *(void **) data = *(void **) val.data; - break; - - case SEQUENCE: - *(sequence_t **) data = new sequence_t(**(sequence_t **) val.data); - break; - - default: - assert(0); - break; - } - - type = val.type; - - return *this; -} - -value_t& value_t::operator+=(const value_t& val) -{ - if (val.type == BOOLEAN) - throw_(value_exception, "Cannot add a boolean to a value"); - else if (val.type == DATETIME) - throw_(value_exception, "Cannot add a date/time to a value"); - else if (val.type == POINTER) - throw_(value_exception, "Cannot add a pointer to a value"); - else if (val.type == SEQUENCE) - throw_(value_exception, "Cannot add a sequence to a value"); - else if (val.type == XML_NODE) // recurse - return *this += (*(xml::node_t **) val.data)->to_value(); - - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot add a value to a boolean"); - - case INTEGER: - switch (val.type) { - case INTEGER: - *((long *) data) += *((long *) val.data); - break; - case AMOUNT: - in_place_cast(AMOUNT); - *((amount_t *) data) += *((amount_t *) val.data); - break; - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) += *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) += *((balance_pair_t *) val.data); - break; - case STRING: - throw_(value_exception, "Cannot add a string to an integer"); - default: - assert(0); - break; - } - break; - - case DATETIME: - switch (val.type) { - case INTEGER: - *((moment_t *) data) += date_duration(*((long *) val.data)); - break; - case AMOUNT: - *((moment_t *) data) += date_duration(long(*((amount_t *) val.data))); - break; - case BALANCE: - *((moment_t *) data) += date_duration(long(*((balance_t *) val.data))); - break; - case BALANCE_PAIR: - *((moment_t *) data) += date_duration(long(*((balance_pair_t *) val.data))); - break; - case STRING: - throw_(value_exception, "Cannot add a string to an date/time"); - default: - assert(0); - break; - } - break; - - case AMOUNT: - switch (val.type) { - case INTEGER: - if (*((long *) val.data) && - ((amount_t *) data)->commodity()) { - in_place_cast(BALANCE); - return *this += val; - } - *((amount_t *) data) += *((long *) val.data); - break; - - case AMOUNT: - if (((amount_t *) data)->commodity() != - ((amount_t *) val.data)->commodity()) { - in_place_cast(BALANCE); - return *this += val; - } - *((amount_t *) data) += *((amount_t *) val.data); - break; - - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) += *((balance_t *) val.data); - break; - - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) += *((balance_pair_t *) val.data); - break; - - case STRING: - throw_(value_exception, "Cannot add a string to an amount"); - - default: - assert(0); - break; - } - break; - - case BALANCE: - switch (val.type) { - case INTEGER: - *((balance_t *) data) += *((long *) val.data); - break; - case AMOUNT: - *((balance_t *) data) += *((amount_t *) val.data); - break; - case BALANCE: - *((balance_t *) data) += *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) += *((balance_pair_t *) val.data); - break; - case STRING: - throw_(value_exception, "Cannot add a string to an balance"); - default: - assert(0); - break; - } - break; - - case BALANCE_PAIR: - switch (val.type) { - case INTEGER: - *((balance_pair_t *) data) += *((long *) val.data); - break; - case AMOUNT: - *((balance_pair_t *) data) += *((amount_t *) val.data); - break; - case BALANCE: - *((balance_pair_t *) data) += *((balance_t *) val.data); - break; - case BALANCE_PAIR: - *((balance_pair_t *) data) += *((balance_pair_t *) val.data); - break; - case STRING: - throw_(value_exception, "Cannot add a string to an balance pair"); - default: - assert(0); - break; - } - break; - - case STRING: - switch (val.type) { - case INTEGER: - throw_(value_exception, "Cannot add an integer to a string"); - case AMOUNT: - throw_(value_exception, "Cannot add an amount to a string"); - case BALANCE: - throw_(value_exception, "Cannot add a balance to a string"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot add a balance pair to a string"); - case STRING: - **(string **) data += **(string **) val.data; - break; - default: - assert(0); - break; - } - break; - - case XML_NODE: - throw_(value_exception, "Cannot add a value to an XML node"); - - case POINTER: - throw_(value_exception, "Cannot add a value to a pointer"); - - case SEQUENCE: - throw_(value_exception, "Cannot add a value to a sequence"); - - default: - assert(0); - break; - } - return *this; -} - -value_t& value_t::operator-=(const value_t& val) -{ - if (val.type == BOOLEAN) - throw_(value_exception, "Cannot subtract a boolean from a value"); - else if (val.type == DATETIME && type != DATETIME) - throw_(value_exception, "Cannot subtract a date/time from a value"); - else if (val.type == STRING) - throw_(value_exception, "Cannot subtract a string from a value"); - else if (val.type == POINTER) - throw_(value_exception, "Cannot subtract a pointer from a value"); - else if (val.type == SEQUENCE) - throw_(value_exception, "Cannot subtract a sequence from a value"); - else if (val.type == XML_NODE) // recurse - return *this -= (*(xml::node_t **) val.data)->to_value(); - - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot subtract a value from a boolean"); - - case INTEGER: - switch (val.type) { - case INTEGER: - *((long *) data) -= *((long *) val.data); - break; - case AMOUNT: - in_place_cast(AMOUNT); - *((amount_t *) data) -= *((amount_t *) val.data); - break; - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) -= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case DATETIME: - switch (val.type) { - case INTEGER: - *((moment_t *) data) -= date_duration(*((long *) val.data)); - break; - case DATETIME: { - duration_t tval = ((moment_t *) data)->operator-(*((moment_t *) val.data)); - in_place_cast(INTEGER); - *((long *) data) = tval.total_seconds() / 86400L; - break; - } - case AMOUNT: - *((moment_t *) data) -= date_duration(long(*((amount_t *) val.data))); - break; - case BALANCE: - *((moment_t *) data) -= date_duration(long(*((balance_t *) val.data))); - break; - case BALANCE_PAIR: - *((moment_t *) data) -= date_duration(long(*((balance_pair_t *) val.data))); - break; - default: - assert(0); - break; - } - break; - - case AMOUNT: - switch (val.type) { - case INTEGER: - if (*((long *) val.data) && - ((amount_t *) data)->commodity()) { - in_place_cast(BALANCE); - return *this -= val; - } - *((amount_t *) data) -= *((long *) val.data); - break; - - case AMOUNT: - if (((amount_t *) data)->commodity() != - ((amount_t *) val.data)->commodity()) { - in_place_cast(BALANCE); - return *this -= val; - } - *((amount_t *) data) -= *((amount_t *) val.data); - break; - - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) -= *((balance_t *) val.data); - break; - - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); - break; - - default: - assert(0); - break; - } - break; - - case BALANCE: - switch (val.type) { - case INTEGER: - *((balance_t *) data) -= *((long *) val.data); - break; - case AMOUNT: - *((balance_t *) data) -= *((amount_t *) val.data); - break; - case BALANCE: - *((balance_t *) data) -= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case BALANCE_PAIR: - switch (val.type) { - case INTEGER: - *((balance_pair_t *) data) -= *((long *) val.data); - break; - case AMOUNT: - *((balance_pair_t *) data) -= *((amount_t *) val.data); - break; - case BALANCE: - *((balance_pair_t *) data) -= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - *((balance_pair_t *) data) -= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case STRING: - throw_(value_exception, "Cannot subtract a value from a string"); - case XML_NODE: - throw_(value_exception, "Cannot subtract a value from an XML node"); - case POINTER: - throw_(value_exception, "Cannot subtract a value from a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot subtract a value from a sequence"); - - default: - assert(0); - break; - } - - simplify(); - - return *this; -} - -value_t& value_t::operator*=(const value_t& val) -{ - if (val.type == BOOLEAN) - throw_(value_exception, "Cannot multiply a value by a boolean"); - else if (val.type == DATETIME) - throw_(value_exception, "Cannot multiply a value by a date/time"); - else if (val.type == STRING) - throw_(value_exception, "Cannot multiply a value by a string"); - else if (val.type == POINTER) - throw_(value_exception, "Cannot multiply a value by a pointer"); - else if (val.type == SEQUENCE) - throw_(value_exception, "Cannot multiply a value by a sequence"); - else if (val.type == XML_NODE) // recurse - return *this *= (*(xml::node_t **) val.data)->to_value(); - - if (val.realzero() && type != STRING) { - *this = 0L; - return *this; - } - - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot multiply a value by a boolean"); - - case INTEGER: - switch (val.type) { - case INTEGER: - *((long *) data) *= *((long *) val.data); - break; - case AMOUNT: - in_place_cast(AMOUNT); - *((amount_t *) data) *= *((amount_t *) val.data); - break; - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) *= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case AMOUNT: - switch (val.type) { - case INTEGER: - *((amount_t *) data) *= *((long *) val.data); - break; - case AMOUNT: - *((amount_t *) data) *= *((amount_t *) val.data); - break; - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) *= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case BALANCE: - switch (val.type) { - case INTEGER: - *((balance_t *) data) *= *((long *) val.data); - break; - case AMOUNT: - *((balance_t *) data) *= *((amount_t *) val.data); - break; - case BALANCE: - *((balance_t *) data) *= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case BALANCE_PAIR: - switch (val.type) { - case INTEGER: - *((balance_pair_t *) data) *= *((long *) val.data); - break; - case AMOUNT: - *((balance_pair_t *) data) *= *((amount_t *) val.data); - break; - case BALANCE: - *((balance_pair_t *) data) *= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - *((balance_pair_t *) data) *= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case STRING: - switch (val.type) { - case INTEGER: { - string temp; - for (long i = 0; i < *(long *) val.data; i++) - temp += **(string **) data; - **(string **) data = temp; - break; - } - case AMOUNT: { - string temp; - value_t num(val); - num.in_place_cast(INTEGER); - for (long i = 0; i < *(long *) num.data; i++) - temp += **(string **) data; - **(string **) data = temp; - break; - } - case BALANCE: - throw_(value_exception, "Cannot multiply a string by a balance"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot multiply a string by a balance pair"); - default: - assert(0); - break; - } - break; - - case XML_NODE: - throw_(value_exception, "Cannot multiply an XML node by a value"); - case POINTER: - throw_(value_exception, "Cannot multiply a pointer by a value"); - case SEQUENCE: - throw_(value_exception, "Cannot multiply a sequence by a value"); - - default: - assert(0); - break; - } - return *this; -} - -value_t& value_t::operator/=(const value_t& val) -{ - if (val.type == BOOLEAN) - throw_(value_exception, "Cannot divide a boolean by a value"); - else if (val.type == DATETIME) - throw_(value_exception, "Cannot divide a date/time by a value"); - else if (val.type == STRING) - throw_(value_exception, "Cannot divide a string by a value"); - else if (val.type == POINTER) - throw_(value_exception, "Cannot divide a pointer by a value"); - else if (val.type == SEQUENCE) - throw_(value_exception, "Cannot divide a value by a sequence"); - else if (val.type == XML_NODE) // recurse - return *this /= (*(xml::node_t **) val.data)->to_value(); - - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot divide a value by a boolean"); - - case INTEGER: - switch (val.type) { - case INTEGER: - *((long *) data) /= *((long *) val.data); - break; - case AMOUNT: - in_place_cast(AMOUNT); - *((amount_t *) data) /= *((amount_t *) val.data); - break; - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) /= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case AMOUNT: - switch (val.type) { - case INTEGER: - *((amount_t *) data) /= *((long *) val.data); - break; - case AMOUNT: - *((amount_t *) data) /= *((amount_t *) val.data); - break; - case BALANCE: - in_place_cast(BALANCE); - *((balance_t *) data) /= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case BALANCE: - switch (val.type) { - case INTEGER: - *((balance_t *) data) /= *((long *) val.data); - break; - case AMOUNT: - *((balance_t *) data) /= *((amount_t *) val.data); - break; - case BALANCE: - *((balance_t *) data) /= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - in_place_cast(BALANCE_PAIR); - *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case BALANCE_PAIR: - switch (val.type) { - case INTEGER: - *((balance_pair_t *) data) /= *((long *) val.data); - break; - case AMOUNT: - *((balance_pair_t *) data) /= *((amount_t *) val.data); - break; - case BALANCE: - *((balance_pair_t *) data) /= *((balance_t *) val.data); - break; - case BALANCE_PAIR: - *((balance_pair_t *) data) /= *((balance_pair_t *) val.data); - break; - default: - assert(0); - break; - } - break; - - case STRING: - throw_(value_exception, "Cannot divide a value from a string"); - case XML_NODE: - throw_(value_exception, "Cannot divide a value from an XML node"); - case POINTER: - throw_(value_exception, "Cannot divide a value from a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot divide a value from a sequence"); - - default: - assert(0); - break; - } - return *this; -} - -template <> -value_t::operator bool() const -{ - switch (type) { - case BOOLEAN: - return *(bool *) data; - case INTEGER: - return *(long *) data; - case DATETIME: - return is_valid_moment(*((moment_t *) data)); - case AMOUNT: - return *(amount_t *) data; - case BALANCE: - return *(balance_t *) data; - case BALANCE_PAIR: - return *(balance_pair_t *) data; - case STRING: - return ! (**((string **) data)).empty(); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().to_boolean(); - case POINTER: - return *(void **) data != NULL; - case SEQUENCE: - return (*(sequence_t **) data != NULL && - ! (*(sequence_t **) data)->empty()); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> -value_t::operator long() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot convert a boolean to an integer"); - case INTEGER: - return *((long *) data); - case DATETIME: - throw_(value_exception, "Cannot convert a date/time to an integer"); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - throw_(value_exception, "Cannot convert a balance to an integer"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a balance pair to an integer"); - case STRING: - throw_(value_exception, "Cannot convert a string to an integer"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().to_integer(); - case POINTER: - throw_(value_exception, "Cannot convert a pointer to an integer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a sequence to an integer"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> -value_t::operator moment_t() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot convert a boolean to a date/time"); - case INTEGER: - throw_(value_exception, "Cannot convert an integer to a date/time"); - case DATETIME: - return *((moment_t *) data); - case AMOUNT: - throw_(value_exception, "Cannot convert an amount to a date/time"); - case BALANCE: - throw_(value_exception, "Cannot convert a balance to a date/time"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a balance pair to a date/time"); - case STRING: - throw_(value_exception, "Cannot convert a string to a date/time"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().to_datetime(); - case POINTER: - throw_(value_exception, "Cannot convert a pointer to a date/time"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a sequence to a date/time"); - - default: - assert(0); - break; - } - assert(0); - return moment_t(); -} - -template <> -value_t::operator double() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot convert a boolean to a double"); - case INTEGER: - return *((long *) data); - case DATETIME: - throw_(value_exception, "Cannot convert a date/time to a double"); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - throw_(value_exception, "Cannot convert a balance to a double"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a balance pair to a double"); - case STRING: - throw_(value_exception, "Cannot convert a string to a double"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().to_amount().number(); - case POINTER: - throw_(value_exception, "Cannot convert a pointer to a double"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a sequence to a double"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> -value_t::operator string() const -{ - switch (type) { - case BOOLEAN: - case INTEGER: - case DATETIME: - case AMOUNT: - case BALANCE: - case BALANCE_PAIR: { - value_t temp(*this); - temp.in_place_cast(STRING); - return temp; - } - case STRING: - return **(string **) data; - case XML_NODE: - return (*(xml::node_t **) data)->to_value().to_string(); - - case POINTER: - throw_(value_exception, "Cannot convert a pointer to a string"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a sequence to a string"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -#define DEF_VALUE_CMP_OP(OP) \ -bool value_t::operator OP(const value_t& val) \ -{ \ - switch (type) { \ - case BOOLEAN: \ - switch (val.type) { \ - case BOOLEAN: \ - return *((bool *) data) OP *((bool *) val.data); \ - \ - case INTEGER: \ - return *((bool *) data) OP bool(*((long *) val.data)); \ - \ - case DATETIME: \ - throw_(value_exception, "Cannot compare a boolean to a date/time"); \ - \ - case AMOUNT: \ - return *((bool *) data) OP bool(*((amount_t *) val.data)); \ - \ - case BALANCE: \ - return *((bool *) data) OP bool(*((balance_t *) val.data)); \ - \ - case BALANCE_PAIR: \ - return *((bool *) data) OP bool(*((balance_pair_t *) val.data)); \ - \ - case STRING: \ - throw_(value_exception, "Cannot compare a boolean to a string"); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare a boolean to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a boolean to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case INTEGER: \ - switch (val.type) { \ - case BOOLEAN: \ - return (*((long *) data) OP \ - ((long) *((bool *) val.data))); \ - \ - case INTEGER: \ - return (*((long *) data) OP *((long *) val.data)); \ - \ - case DATETIME: \ - throw_(value_exception, "Cannot compare an integer to a date/time"); \ - \ - case AMOUNT: \ - return (amount_t(*((long *) data)) OP \ - *((amount_t *) val.data)); \ - \ - case BALANCE: \ - return (balance_t(*((long *) data)) OP \ - *((balance_t *) val.data)); \ - \ - case BALANCE_PAIR: \ - return (balance_pair_t(*((long *) data)) OP \ - *((balance_pair_t *) val.data)); \ - \ - case STRING: \ - throw_(value_exception, "Cannot compare an integer to a string"); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare an integer to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare an integer to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case DATETIME: \ - switch (val.type) { \ - case BOOLEAN: \ - throw_(value_exception, "Cannot compare a date/time to a boolean"); \ - \ - case INTEGER: \ - throw_(value_exception, "Cannot compare a date/time to an integer"); \ - \ - case DATETIME: \ - return *((moment_t *) data) OP *((moment_t *) val.data); \ - \ - case AMOUNT: \ - throw_(value_exception, "Cannot compare a date/time to an amount"); \ - case BALANCE: \ - throw_(value_exception, "Cannot compare a date/time to a balance"); \ - case BALANCE_PAIR: \ - throw_(value_exception, "Cannot compare a date/time to a balance pair"); \ - case STRING: \ - throw_(value_exception, "Cannot compare a date/time to a string"); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare a date/time to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a date/time to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case AMOUNT: \ - switch (val.type) { \ - case BOOLEAN: \ - throw_(value_exception, "Cannot compare an amount to a boolean"); \ - \ - case INTEGER: \ - return (*((amount_t *) data) OP \ - amount_t(*((long *) val.data))); \ - \ - case DATETIME: \ - throw_(value_exception, "Cannot compare an amount to a date/time"); \ - \ - case AMOUNT: \ - return *((amount_t *) data) OP *((amount_t *) val.data); \ - \ - case BALANCE: \ - return (balance_t(*((amount_t *) data)) OP \ - *((balance_t *) val.data)); \ - \ - case BALANCE_PAIR: \ - return (balance_t(*((amount_t *) data)) OP \ - *((balance_pair_t *) val.data)); \ - \ - case STRING: \ - throw_(value_exception, "Cannot compare an amount to a string"); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare an amount to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare an amount to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE: \ - switch (val.type) { \ - case BOOLEAN: \ - throw_(value_exception, "Cannot compare a balance to a boolean"); \ - \ - case INTEGER: \ - return *((balance_t *) data) OP *((long *) val.data); \ - \ - case DATETIME: \ - throw_(value_exception, "Cannot compare a balance to a date/time"); \ - \ - case AMOUNT: \ - return *((balance_t *) data) OP *((amount_t *) val.data); \ - \ - case BALANCE: \ - return *((balance_t *) data) OP *((balance_t *) val.data); \ - \ - case BALANCE_PAIR: \ - return (*((balance_t *) data) OP \ - ((balance_pair_t *) val.data)->quantity); \ - \ - case STRING: \ - throw_(value_exception, "Cannot compare a balance to a string"); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare a balance to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a balance to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case BALANCE_PAIR: \ - switch (val.type) { \ - case BOOLEAN: \ - throw_(value_exception, "Cannot compare a balance pair to a boolean"); \ - \ - case INTEGER: \ - return (((balance_pair_t *) data)->quantity OP \ - *((long *) val.data)); \ - \ - case DATETIME: \ - throw_(value_exception, "Cannot compare a balance pair to a date/time"); \ - \ - case AMOUNT: \ - return (((balance_pair_t *) data)->quantity OP \ - *((amount_t *) val.data)); \ - \ - case BALANCE: \ - return (((balance_pair_t *) data)->quantity OP \ - *((balance_t *) val.data)); \ - \ - case BALANCE_PAIR: \ - return (*((balance_pair_t *) data) OP \ - *((balance_pair_t *) val.data)); \ - \ - case STRING: \ - throw_(value_exception, "Cannot compare a balance pair to a string"); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare a balance pair to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a balance pair to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case STRING: \ - switch (val.type) { \ - case BOOLEAN: \ - throw_(value_exception, "Cannot compare a string to a boolean"); \ - case INTEGER: \ - throw_(value_exception, "Cannot compare a string to an integer"); \ - case DATETIME: \ - throw_(value_exception, "Cannot compare a string to a date/time"); \ - case AMOUNT: \ - throw_(value_exception, "Cannot compare a string to an amount"); \ - case BALANCE: \ - throw_(value_exception, "Cannot compare a string to a balance"); \ - case BALANCE_PAIR: \ - throw_(value_exception, "Cannot compare a string to a balance pair"); \ - \ - case STRING: \ - return (**((string **) data) OP \ - **((string **) val.data)); \ - \ - case XML_NODE: \ - return *this OP (*(xml::node_t **) data)->to_value(); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare a string to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a string to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case XML_NODE: \ - switch (val.type) { \ - case BOOLEAN: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - case INTEGER: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - case DATETIME: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - case AMOUNT: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - case BALANCE: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - case BALANCE_PAIR: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - case STRING: \ - return (*(xml::node_t **) data)->to_value() OP *this; \ - \ - case XML_NODE: \ - return ((*(xml::node_t **) data)->to_value() OP \ - (*(xml::node_t **) val.data)->to_value()); \ - \ - case POINTER: \ - throw_(value_exception, "Cannot compare an XML node to a pointer"); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare an XML node to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case POINTER: \ - switch (val.type) { \ - case BOOLEAN: \ - throw_(value_exception, "Cannot compare a pointer to a boolean"); \ - case INTEGER: \ - throw_(value_exception, "Cannot compare a pointer to an integer"); \ - case DATETIME: \ - throw_(value_exception, "Cannot compare a pointer to a date/time"); \ - case AMOUNT: \ - throw_(value_exception, "Cannot compare a pointer to an amount"); \ - case BALANCE: \ - throw_(value_exception, "Cannot compare a pointer to a balance"); \ - case BALANCE_PAIR: \ - throw_(value_exception, "Cannot compare a pointer to a balance pair"); \ - case STRING: \ - throw_(value_exception, "Cannot compare a pointer to a string node"); \ - case XML_NODE: \ - throw_(value_exception, "Cannot compare a pointer to an XML node"); \ - case POINTER: \ - return (*((void **) data) OP *((void **) val.data)); \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a pointer to a sequence"); \ - \ - default: \ - assert(0); \ - break; \ - } \ - break; \ - \ - case SEQUENCE: \ - throw_(value_exception, "Cannot compare a value to a sequence"); \ - \ - 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(>=) - -void value_t::in_place_cast(type_t cast_type) -{ - switch (type) { - case BOOLEAN: - switch (cast_type) { - case BOOLEAN: - break; - case INTEGER: - throw_(value_exception, "Cannot convert a boolean to an integer"); - case DATETIME: - throw_(value_exception, "Cannot convert a boolean to a date/time"); - case AMOUNT: - throw_(value_exception, "Cannot convert a boolean to an amount"); - case BALANCE: - throw_(value_exception, "Cannot convert a boolean to a balance"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a boolean to a balance pair"); - case STRING: - *(string **) data = new string(*((bool *) data) ? "true" : "false"); - break; - case XML_NODE: - throw_(value_exception, "Cannot convert a boolean to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert a boolean to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a boolean to a sequence"); - - default: - assert(0); - break; - } - break; - - case INTEGER: - switch (cast_type) { - case BOOLEAN: - *((bool *) data) = *((long *) data); - break; - case INTEGER: - break; - case DATETIME: - throw_(value_exception, "Cannot convert an integer to a date/time"); - - case AMOUNT: - new((amount_t *)data) amount_t(*((long *) data)); - break; - case BALANCE: - new((balance_t *)data) balance_t(*((long *) data)); - break; - case BALANCE_PAIR: - new((balance_pair_t *)data) balance_pair_t(*((long *) data)); - break; - case STRING: { - char buf[32]; - std::sprintf(buf, "%ld", *(long *) data); - *(string **) data = new string(buf); - break; - } - case XML_NODE: - throw_(value_exception, "Cannot convert an integer to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert an integer to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert an integer to a sequence"); - - default: - assert(0); - break; - } - break; - - case DATETIME: - switch (cast_type) { - case BOOLEAN: - *((bool *) data) = is_valid_moment(*((moment_t *) data)); - break; - case INTEGER: - throw_(value_exception, "Cannot convert a date/time to an integer"); - case DATETIME: - break; - case AMOUNT: - throw_(value_exception, "Cannot convert a date/time to an amount"); - case BALANCE: - throw_(value_exception, "Cannot convert a date/time to a balance"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a date/time to a balance pair"); - case STRING: - throw_(value_exception, "Cannot convert a date/time to a string"); - case XML_NODE: - throw_(value_exception, "Cannot convert a date/time to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert a date/time to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a date/time to a sequence"); - - 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_(value_exception, "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; - } - case STRING: { - std::ostringstream out; - out << *(amount_t *) data; - destroy(); - *(string **) data = new string(out.str()); - break; - } - case XML_NODE: - throw_(value_exception, "Cannot convert an amount to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert an amount to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert an amount to a sequence"); - - 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_(value_exception, "Cannot convert a balance to an integer"); - case DATETIME: - throw_(value_exception, "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_(value_exception, "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; - } - case STRING: - throw_(value_exception, "Cannot convert a balance to a string"); - case XML_NODE: - throw_(value_exception, "Cannot convert a balance to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert a balance to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a balance to a sequence"); - - 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_(value_exception, "Cannot convert a balance pair to an integer"); - case DATETIME: - throw_(value_exception, "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_(value_exception, "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; - case STRING: - throw_(value_exception, "Cannot convert a balance pair to a string"); - case XML_NODE: - throw_(value_exception, "Cannot convert a balance pair to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert a balance pair to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a balance pair to a sequence"); - - default: - assert(0); - break; - } - break; - - case STRING: - switch (cast_type) { - case BOOLEAN: { - if (**(string **) data == "true") { - destroy(); - *(bool *) data = true; - } - else if (**(string **) data == "false") { - destroy(); - *(bool *) data = false; - } - else { - throw_(value_exception, "Cannot convert string to an boolean"); - } - break; - } - case INTEGER: { - int l = (*(string **) data)->length(); - const char * p = (*(string **) data)->c_str(); - bool alldigits = true; - for (int i = 0; i < l; i++) - if (! std::isdigit(p[i])) { - alldigits = false; - break; - } - if (alldigits) { - long temp = std::atol((*(string **) data)->c_str()); - destroy(); - *(long *) data = temp; - } else { - throw_(value_exception, "Cannot convert string to an integer"); - } - break; - } - - case DATETIME: - throw_(value_exception, "Cannot convert a string to a date/time"); - - case AMOUNT: { - amount_t temp = **(string **) data; - destroy(); - new((amount_t *)data) amount_t(temp); - break; - } - case BALANCE: - throw_(value_exception, "Cannot convert a string to a balance"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a string to a balance pair"); - case STRING: - break; - case XML_NODE: - throw_(value_exception, "Cannot convert a string to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert a string to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert a string to a sequence"); - - default: - assert(0); - break; - } - break; - - case XML_NODE: - switch (cast_type) { - case BOOLEAN: - case INTEGER: - case DATETIME: - case AMOUNT: - case BALANCE: - case BALANCE_PAIR: - case STRING: - *this = (*(xml::node_t **) data)->to_value(); - break; - case XML_NODE: - break; - case POINTER: - throw_(value_exception, "Cannot convert an XML node to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot convert an XML node to a sequence"); - - default: - assert(0); - break; - } - break; - - case POINTER: - switch (cast_type) { - case BOOLEAN: - throw_(value_exception, "Cannot convert a pointer to a boolean"); - case INTEGER: - throw_(value_exception, "Cannot convert a pointer to an integer"); - case DATETIME: - throw_(value_exception, "Cannot convert a pointer to a date/time"); - case AMOUNT: - throw_(value_exception, "Cannot convert a pointer to an amount"); - case BALANCE: - throw_(value_exception, "Cannot convert a pointer to a balance"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a pointer to a balance pair"); - case STRING: - throw_(value_exception, "Cannot convert a pointer to a string"); - case XML_NODE: - throw_(value_exception, "Cannot convert a pointer to an XML node"); - case POINTER: - break; - case SEQUENCE: - throw_(value_exception, "Cannot convert a pointer to a sequence"); - - default: - assert(0); - break; - } - break; - - case SEQUENCE: - switch (cast_type) { - case BOOLEAN: - throw_(value_exception, "Cannot convert a sequence to a boolean"); - case INTEGER: - throw_(value_exception, "Cannot convert a sequence to an integer"); - case DATETIME: - throw_(value_exception, "Cannot convert a sequence to a date/time"); - case AMOUNT: - throw_(value_exception, "Cannot convert a sequence to an amount"); - case BALANCE: - throw_(value_exception, "Cannot convert a sequence to a balance"); - case BALANCE_PAIR: - throw_(value_exception, "Cannot convert a sequence to a balance pair"); - case STRING: - throw_(value_exception, "Cannot convert a sequence to a string"); - case XML_NODE: - throw_(value_exception, "Cannot compare a sequence to an XML node"); - case POINTER: - throw_(value_exception, "Cannot convert a sequence to a pointer"); - case SEQUENCE: - break; - - default: - assert(0); - break; - } - break; - - default: - assert(0); - break; - } - type = cast_type; -} - -void value_t::in_place_negate() -{ - switch (type) { - case BOOLEAN: - *((bool *) data) = ! *((bool *) data); - break; - case INTEGER: - *((long *) data) = - *((long *) data); - break; - case DATETIME: - throw_(value_exception, "Cannot negate a date/time"); - case AMOUNT: - ((amount_t *) data)->in_place_negate(); - break; - case BALANCE: - ((balance_t *) data)->in_place_negate(); - break; - case BALANCE_PAIR: - ((balance_pair_t *) data)->in_place_negate(); - break; - case STRING: - throw_(value_exception, "Cannot negate a string"); - case XML_NODE: - *this = (*(xml::node_t **) data)->to_value(); - in_place_negate(); - break; - case POINTER: - throw_(value_exception, "Cannot negate a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot negate a sequence"); - - default: - assert(0); - break; - } -} - -void value_t::in_place_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; - case STRING: - throw_(value_exception, "Cannot take the absolute value of a string"); - case XML_NODE: - *this = (*(xml::node_t **) data)->to_value(); - in_place_abs(); - break; - case POINTER: - throw_(value_exception, "Cannot take the absolute value of a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot take the absolute value of a sequence"); - - default: - assert(0); - break; - } -} - -value_t value_t::value(const moment_t& moment) const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot find the value of a boolean"); - case DATETIME: - throw_(value_exception, "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); - case STRING: - throw_(value_exception, "Cannot find the value of a string"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().value(moment); - case POINTER: - throw_(value_exception, "Cannot find the value of a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot find the value of a sequence"); - default: - assert(0); - return value_t(); - } -} - -void value_t::in_place_reduce() -{ - switch (type) { - case BOOLEAN: - case DATETIME: - case INTEGER: - break; - case AMOUNT: - ((amount_t *) data)->in_place_reduce(); - break; - case BALANCE: - ((balance_t *) data)->in_place_reduce(); - break; - case BALANCE_PAIR: - ((balance_pair_t *) data)->in_place_reduce(); - break; - case STRING: - throw_(value_exception, "Cannot reduce a string"); - case XML_NODE: - *this = (*(xml::node_t **) data)->to_value(); - in_place_reduce(); // recurse - break; - case POINTER: - throw_(value_exception, "Cannot reduce a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot reduce a sequence"); - } -} - -value_t value_t::round() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot round a boolean"); - case DATETIME: - throw_(value_exception, "Cannot round a date/time"); - case INTEGER: - return *this; - case AMOUNT: - return ((amount_t *) data)->round(); - case BALANCE: - return ((balance_t *) data)->round(); - case BALANCE_PAIR: - return ((balance_pair_t *) data)->round(); - case STRING: - throw_(value_exception, "Cannot round a string"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().round(); - case POINTER: - throw_(value_exception, "Cannot round a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot round a sequence"); - } - assert(0); - return value_t(); -} - -value_t value_t::unround() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot un-round a boolean"); - case DATETIME: - throw_(value_exception, "Cannot un-round a date/time"); - case INTEGER: - return *this; - case AMOUNT: - return ((amount_t *) data)->unround(); - case BALANCE: - return ((balance_t *) data)->unround(); - case BALANCE_PAIR: - return ((balance_pair_t *) data)->unround(); - case STRING: - throw_(value_exception, "Cannot un-round a string"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().unround(); - case POINTER: - throw_(value_exception, "Cannot un-round a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot un-round a sequence"); - } - assert(0); - return value_t(); -} - -value_t value_t::price() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot find the price of a boolean"); - case INTEGER: - return *this; - case DATETIME: - throw_(value_exception, "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(); - - case STRING: - throw_(value_exception, "Cannot find the price of a string"); - - case XML_NODE: - return (*(xml::node_t **) data)->to_value().price(); - - case POINTER: - throw_(value_exception, "Cannot find the price of a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot find the price of a sequence"); - - default: - assert(0); - break; - } - assert(0); - return value_t(); -} - -value_t value_t::date() const -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot find the date of a boolean"); - case INTEGER: - throw_(value_exception, "Cannot find the date of an integer"); - - case DATETIME: - return *this; - - case AMOUNT: - return ((amount_t *) data)->date(); - case BALANCE: - return ((balance_t *) data)->date(); - case BALANCE_PAIR: - return ((balance_pair_t *) data)->quantity.date(); - - case STRING: - throw_(value_exception, "Cannot find the date of a string"); - - case XML_NODE: - return (*(xml::node_t **) data)->to_value().date(); - - case POINTER: - throw_(value_exception, "Cannot find the date of a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot find the date of a sequence"); - - 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: - case STRING: - case XML_NODE: - case POINTER: - return *this; - - case SEQUENCE: - assert(0); // jww (2006-09-28): strip them all! - break; - - 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_(value_exception, "Cannot find the cost of a boolean"); - case INTEGER: - case AMOUNT: - case BALANCE: - return *this; - case DATETIME: - throw_(value_exception, "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; - - case STRING: - throw_(value_exception, "Cannot find the cost of a string"); - case XML_NODE: - return (*(xml::node_t **) data)->to_value().cost(); - case POINTER: - throw_(value_exception, "Cannot find the cost of a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot find the cost of a sequence"); - - default: - assert(0); - break; - } - assert(0); - return value_t(); -} - -value_t& value_t::add(const amount_t& amount, const amount_t * tcost) -{ - switch (type) { - case BOOLEAN: - throw_(value_exception, "Cannot add an amount to a boolean"); - case DATETIME: - throw_(value_exception, "Cannot add an amount to a date/time"); - case INTEGER: - case AMOUNT: - if (tcost) { - in_place_cast(BALANCE_PAIR); - return add(amount, tcost); - } - else if ((type == AMOUNT && - ((amount_t *) data)->commodity() != amount.commodity()) || - (type != AMOUNT && amount.commodity())) { - in_place_cast(BALANCE); - return add(amount, tcost); - } - else if (type != AMOUNT) { - in_place_cast(AMOUNT); - } - *((amount_t *) data) += amount; - break; - - case BALANCE: - if (tcost) { - in_place_cast(BALANCE_PAIR); - return add(amount, tcost); - } - *((balance_t *) data) += amount; - break; - - case BALANCE_PAIR: - ((balance_pair_t *) data)->add(amount, tcost); - break; - - case STRING: - throw_(value_exception, "Cannot add an amount to a string"); - case XML_NODE: - throw_(value_exception, "Cannot add an amount to an XML node"); - case POINTER: - throw_(value_exception, "Cannot add an amount to a pointer"); - case SEQUENCE: - throw_(value_exception, "Cannot add an amount to a sequence"); - - default: - assert(0); - break; - } - - return *this; -} - -void value_t::write(std::ostream& out, const int first_width, - const int latter_width) const -{ - switch (type) { - case BOOLEAN: - case DATETIME: - case INTEGER: - case AMOUNT: - case STRING: - case POINTER: - out << *this; - break; - - case XML_NODE: - (*(xml::node_t **) data)->write(out); - break; - - case SEQUENCE: - assert(0); // jww (2006-09-28): write them all out! - throw_(value_exception, "Cannot write out a sequence"); - - case BALANCE: - ((balance_t *) data)->write(out, first_width, latter_width); - break; - case BALANCE_PAIR: - ((balance_pair_t *) data)->write(out, first_width, latter_width); - break; - } -} - -std::ostream& operator<<(std::ostream& out, const value_t& val) -{ - switch (val.type) { - case value_t::BOOLEAN: - out << (*((bool *) val.data) ? "true" : "false"); - break; - case value_t::INTEGER: - out << *(long *) val.data; - break; - case value_t::DATETIME: - out << *(moment_t *) val.data; - break; - case value_t::AMOUNT: - out << *(amount_t *) val.data; - break; - case value_t::BALANCE: - out << *(balance_t *) val.data; - break; - case value_t::BALANCE_PAIR: - out << *(balance_pair_t *) val.data; - break; - case value_t::STRING: - out << **(string **) val.data; - break; - case value_t::XML_NODE: - if ((*(xml::node_t **) val.data)->flags & XML_NODE_IS_PARENT) - out << '<' << (*(xml::node_t **) val.data)->name() << '>'; - else - out << (*(xml::node_t **) val.data)->text(); - break; - - case value_t::POINTER: - throw_(value_exception, "Cannot output a pointer value"); - - case value_t::SEQUENCE: { - out << '('; - bool first = true; - for (value_t::sequence_t::iterator - i = (*(value_t::sequence_t **) val.data)->begin(); - i != (*(value_t::sequence_t **) val.data)->end(); - i++) { - if (first) - first = false; - else - out << ", "; - out << *i; - } - out << ')'; - break; - } - - default: - assert(0); - break; - } - return out; -} - -#if 0 -value_context::value_context(const value_t& _bal, - const string& _desc) throw() - : error_context(_desc), bal(new value_t(_bal)) {} - -value_context::~value_context() throw() -{ - delete bal; -} - -void value_context::describe(std::ostream& out) const throw() -{ - if (! desc.empty()) - out << desc << std::endl; - - balance_t * ptr = NULL; - - out << std::right; - out.width(20); - - switch (bal->type) { - case value_t::BOOLEAN: - out << (*((bool *) bal->data) ? "true" : "false"); - break; - case value_t::INTEGER: - out << *((long *) bal->data); - break; - case value_t::DATETIME: - out << *((moment_t *) bal->data); - break; - case value_t::AMOUNT: - out << *((amount_t *) bal->data); - break; - case value_t::BALANCE: - ptr = (balance_t *) bal->data; - // fall through... - - case value_t::BALANCE_PAIR: - if (! ptr) - ptr = &((balance_pair_t *) bal->data)->quantity; - - ptr->write(out, 20); - break; - default: - assert(0); - break; - } - out << std::endl; -} -#endif - -} // namespace ledger diff --git a/value.h b/value.h deleted file mode 100644 index 2b0adf2b..00000000 --- a/value.h +++ /dev/null @@ -1,580 +0,0 @@ -#ifndef _VALUE_H -#define _VALUE_H - -#include "amount.h" -#include "balance.h" - -namespace ledger { - -namespace xml { - class node_t; -} - -// 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: - typedef std::vector sequence_t; - - char data[sizeof(balance_pair_t)]; - - enum type_t { - BOOLEAN, - INTEGER, - DATETIME, - AMOUNT, - BALANCE, - BALANCE_PAIR, - STRING, - XML_NODE, - POINTER, - SEQUENCE - } type; - - value_t() { - TRACE_CTOR(value_t, ""); - *((long *) data) = 0; - type = INTEGER; - } - - value_t(const value_t& val) : type(INTEGER) { - TRACE_CTOR(value_t, "copy"); - *this = val; - } - value_t(const bool val) { - TRACE_CTOR(value_t, "const bool"); - *((bool *) data) = val; - type = BOOLEAN; - } - value_t(const long val) { - TRACE_CTOR(value_t, "const long"); - *((long *) data) = val; - type = INTEGER; - } - value_t(const moment_t val) { - TRACE_CTOR(value_t, "const moment_t"); - *((moment_t *) data) = val; - type = DATETIME; - } - value_t(const unsigned long val) { - TRACE_CTOR(value_t, "const unsigned long"); - new((amount_t *) data) amount_t(val); - type = AMOUNT; - } - value_t(const double val) { - TRACE_CTOR(value_t, "const double"); - new((amount_t *) data) amount_t(val); - type = AMOUNT; - } - value_t(const string& val, bool literal = false) { - TRACE_CTOR(value_t, "const string&, bool"); - if (literal) { - type = INTEGER; - set_string(val); - } else { - new((amount_t *) data) amount_t(val); - type = AMOUNT; - } - } - value_t(const char * val) { - TRACE_CTOR(value_t, "const char *"); - new((amount_t *) data) amount_t(val); - type = AMOUNT; - } - value_t(const amount_t& val) { - TRACE_CTOR(value_t, "const amount_t&"); - new((amount_t *)data) amount_t(val); - type = AMOUNT; - } - value_t(const balance_t& val) : type(INTEGER) { - TRACE_CTOR(value_t, "const balance_t&"); - *this = val; - } - value_t(const balance_pair_t& val) : type(INTEGER) { - TRACE_CTOR(value_t, "const balance_pair_t&"); - *this = val; - } - value_t(xml::node_t * xml_node) : type(INTEGER) { // gets set in = - TRACE_CTOR(value_t, "xml::node_t *"); - *this = xml_node; - } - value_t(void * item) : type(INTEGER) { // gets set in = - TRACE_CTOR(value_t, "void *"); - *this = item; - } - value_t(sequence_t * seq) : type(INTEGER) { // gets set in = - TRACE_CTOR(value_t, "sequence_t *"); - *this = seq; - } - - ~value_t() { - TRACE_DTOR(value_t); - destroy(); - } - - void destroy(); - void simplify(); - - value_t& operator=(const value_t& val); - value_t& operator=(const bool val) { - if ((bool *) data != &val) { - destroy(); - *((bool *) data) = val; - type = BOOLEAN; - } - return *this; - } - value_t& operator=(const long val) { - if ((long *) data != &val) { - destroy(); - *((long *) data) = val; - type = INTEGER; - } - return *this; - } - value_t& operator=(const moment_t val) { - if ((moment_t *) data != &val) { - destroy(); - *((moment_t *) data) = val; - type = DATETIME; - } - return *this; - } - value_t& operator=(const unsigned long val) { - return *this = amount_t(val); - } - value_t& operator=(const double val) { - return *this = amount_t(val); - } - value_t& operator=(const string& val) { - return *this = amount_t(val); - } - value_t& operator=(const char * val) { - return *this = amount_t(val); - } - value_t& operator=(const amount_t& val) { - if (type == AMOUNT && - (amount_t *) data == &val) - return *this; - - if (val.realzero()) { - return *this = 0L; - } else { - destroy(); - new((amount_t *)data) amount_t(val); - type = AMOUNT; - } - return *this; - } - value_t& operator=(const balance_t& val) { - if (type == BALANCE && - (balance_t *) data == &val) - return *this; - - if (val.realzero()) { - return *this = 0L; - } - else if (val.amounts.size() == 1) { - return *this = (*val.amounts.begin()).second; - } - else { - destroy(); - new((balance_t *)data) balance_t(val); - type = BALANCE; - return *this; - } - } - value_t& operator=(const balance_pair_t& val) { - if (type == BALANCE_PAIR && - (balance_pair_t *) data == &val) - return *this; - - if (val.realzero()) { - return *this = 0L; - } - else if (! val.cost) { - return *this = val.quantity; - } - else { - destroy(); - new((balance_pair_t *)data) balance_pair_t(val); - type = BALANCE_PAIR; - return *this; - } - } - value_t& operator=(xml::node_t * xml_node) { - assert(xml_node); - if (type == XML_NODE && *(xml::node_t **) data == xml_node) - return *this; - - if (! xml_node) { - type = XML_NODE; - return *this = 0L; - } - else { - destroy(); - *(xml::node_t **)data = xml_node; - type = XML_NODE; - return *this; - } - } - value_t& operator=(void * item) { - assert(item); - if (type == POINTER && *(void **) data == item) - return *this; - - if (! item) { - type = POINTER; - return *this = 0L; - } - else { - destroy(); - *(void **)data = item; - type = POINTER; - return *this; - } - } - value_t& operator=(sequence_t * seq) { - assert(seq); - if (type == SEQUENCE && *(sequence_t **) data == seq) - return *this; - - if (! seq) { - type = SEQUENCE; - return *this = 0L; - } - else { - destroy(); - *(sequence_t **)data = seq; - type = SEQUENCE; - return *this; - } - } - - value_t& set_string(const string& str = "") { - if (type != STRING) { - destroy(); - *(string **) data = new string(str); - type = STRING; - } else { - **(string **) data = str; - } - return *this; - } - - bool to_boolean() const; - long to_integer() const; - moment_t to_datetime() const; - amount_t to_amount() const; - balance_t to_balance() const; - balance_pair_t to_balance_pair() const; - string to_string() const; - xml::node_t * to_xml_node() const; - void * to_pointer() const; - sequence_t * to_sequence() const; - - value_t& operator[](const int index) { - sequence_t * seq = to_sequence(); - assert(seq); - return (*seq)[index]; - } - - void push_back(const value_t& val) { - sequence_t * seq = to_sequence(); - assert(seq); - return seq->push_back(val); - } - - std::size_t size() const { - sequence_t * seq = to_sequence(); - assert(seq); - return seq->size(); - } - - 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); - - template - value_t& operator+=(const T& val) { - return *this += value_t(val); - } - template - value_t& operator-=(const T& val) { - return *this -= value_t(val); - } - template - value_t& operator*=(const T& val) { - return *this *= value_t(val); - } - template - value_t& operator/=(const T& val) { - return *this /= value_t(val); - } - - value_t operator+(const value_t& val) { - value_t temp(*this); - temp += val; - return temp; - } - value_t operator-(const value_t& val) { - value_t temp(*this); - temp -= val; - return temp; - } - value_t operator*(const value_t& val) { - value_t temp(*this); - temp *= val; - return temp; - } - value_t operator/(const value_t& val) { - value_t temp(*this); - temp /= val; - return temp; - } - - template - value_t operator+(const T& val) { - return *this + value_t(val); - } - template - value_t operator-(const T& val) { - return *this - value_t(val); - } - template - value_t operator*(const T& val) { - return *this * value_t(val); - } - template - value_t operator/(const T& val) { - return *this / value_t(val); - } - - bool operator<(const value_t& val); - bool operator<=(const value_t& val); - bool operator>(const value_t& val); - bool operator>=(const value_t& val); - bool operator==(const value_t& val); - bool operator!=(const value_t& val) { - return ! (*this == val); - } - - template - bool operator<(const T& val) { - return *this < value_t(val); - } - template - bool operator<=(const T& val) { - return *this <= value_t(val); - } - template - bool operator>(const T& val) { - return *this > value_t(val); - } - template - bool operator>=(const T& val) { - return *this >= value_t(val); - } - template - bool operator==(const T& val) { - return *this == value_t(val); - } - template - bool operator!=(const T& val) { - return ! (*this == val); - } - - template - operator T() const; - - void in_place_negate(); - value_t negate() const { - value_t temp = *this; - temp.in_place_negate(); - return temp; - } - value_t operator-() const { - return negate(); - } - - bool realzero() const { - switch (type) { - case BOOLEAN: - return ! *((bool *) data); - case INTEGER: - return *((long *) data) == 0; - case DATETIME: - return ! is_valid_moment(*((moment_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(); - case STRING: - return ((string *) data)->empty(); - case XML_NODE: - case POINTER: - case SEQUENCE: - return *(void **) data == NULL; - - default: - assert(0); - break; - } - assert(0); - return 0; - } - - void in_place_abs(); - value_t abs() const; - void in_place_cast(type_t cast_type); - value_t cost() const; - value_t price() const; - value_t date() const; - - value_t cast(type_t cast_type) const { - value_t temp(*this); - temp.in_place_cast(cast_type); - return temp; - } - - 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 moment_t& moment) const; - void in_place_reduce(); - - value_t reduce() const { - value_t temp(*this); - temp.in_place_reduce(); - return temp; - } - - value_t round() const; - value_t unround() const; - - void write(std::ostream& out, const int first_width, - const int latter_width = -1) const; -}; - -#define DEFINE_VALUE_OPERATORS(T, OP) \ -inline value_t operator OP(const T& val, const value_t& obj) { \ - return value_t(val) OP obj; \ -} - -DEFINE_VALUE_OPERATORS(bool, ==) -DEFINE_VALUE_OPERATORS(bool, !=) - -DEFINE_VALUE_OPERATORS(long, +) -DEFINE_VALUE_OPERATORS(long, -) -DEFINE_VALUE_OPERATORS(long, *) -DEFINE_VALUE_OPERATORS(long, /) -DEFINE_VALUE_OPERATORS(long, <) -DEFINE_VALUE_OPERATORS(long, <=) -DEFINE_VALUE_OPERATORS(long, >) -DEFINE_VALUE_OPERATORS(long, >=) -DEFINE_VALUE_OPERATORS(long, ==) -DEFINE_VALUE_OPERATORS(long, !=) - -DEFINE_VALUE_OPERATORS(amount_t, +) -DEFINE_VALUE_OPERATORS(amount_t, -) -DEFINE_VALUE_OPERATORS(amount_t, *) -DEFINE_VALUE_OPERATORS(amount_t, /) -DEFINE_VALUE_OPERATORS(amount_t, <) -DEFINE_VALUE_OPERATORS(amount_t, <=) -DEFINE_VALUE_OPERATORS(amount_t, >) -DEFINE_VALUE_OPERATORS(amount_t, >=) -DEFINE_VALUE_OPERATORS(amount_t, ==) -DEFINE_VALUE_OPERATORS(amount_t, !=) - -DEFINE_VALUE_OPERATORS(balance_t, +) -DEFINE_VALUE_OPERATORS(balance_t, -) -DEFINE_VALUE_OPERATORS(balance_t, *) -DEFINE_VALUE_OPERATORS(balance_t, /) -DEFINE_VALUE_OPERATORS(balance_t, <) -DEFINE_VALUE_OPERATORS(balance_t, <=) -DEFINE_VALUE_OPERATORS(balance_t, >) -DEFINE_VALUE_OPERATORS(balance_t, >=) -DEFINE_VALUE_OPERATORS(balance_t, ==) -DEFINE_VALUE_OPERATORS(balance_t, !=) - -DEFINE_VALUE_OPERATORS(balance_pair_t, +) -DEFINE_VALUE_OPERATORS(balance_pair_t, -) -DEFINE_VALUE_OPERATORS(balance_pair_t, *) -DEFINE_VALUE_OPERATORS(balance_pair_t, /) -DEFINE_VALUE_OPERATORS(balance_pair_t, <) -DEFINE_VALUE_OPERATORS(balance_pair_t, <=) -DEFINE_VALUE_OPERATORS(balance_pair_t, >) -DEFINE_VALUE_OPERATORS(balance_pair_t, >=) -DEFINE_VALUE_OPERATORS(balance_pair_t, ==) -DEFINE_VALUE_OPERATORS(balance_pair_t, !=) - -template -value_t::operator T() const -{ - switch (type) { - case BOOLEAN: - return *(bool *) data; - case INTEGER: - return *(long *) data; - case DATETIME: - return *(moment_t *) data; - case AMOUNT: - return *(amount_t *) data; - case BALANCE: - return *(balance_t *) data; - case STRING: - return **(string **) data; - case XML_NODE: - return *(xml::node_t **) data; - case POINTER: - return *(void **) data; - case SEQUENCE: - return *(sequence_t **) data; - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> value_t::operator bool() const; -template <> value_t::operator long() const; -template <> value_t::operator moment_t() const; -template <> value_t::operator double() const; -template <> value_t::operator string() const; - -std::ostream& operator<<(std::ostream& out, const value_t& val); - -#if 0 -class value_context : public error_context -{ - value_t * bal; - public: - value_context(const value_t& _bal, - const string& desc = "") throw(); - virtual ~value_context() throw(); - - virtual void describe(std::ostream& out) const throw(); -}; -#endif - -DECLARE_EXCEPTION(value_exception); - -} // namespace ledger - -#endif // _VALUE_H diff --git a/xml.cc b/xml.cc deleted file mode 100644 index 3b791892..00000000 --- a/xml.cc +++ /dev/null @@ -1,550 +0,0 @@ -#include "xml.h" -#include "journal.h" - -namespace ledger { -namespace xml { - -const std::size_t document_t::ledger_builtins_size = 12; -const char * document_t::ledger_builtins[] = { - "account", - "account-path", - "amount", - "code", - "commodity", - "entries", - "entry", - "journal", - "name", - "note", - "payee", - "transaction" -}; - -document_t::document_t(node_t * _top) - : stub(this), top(_top ? _top : &stub) { - TRACE_CTOR(xml::document_t, "node_t *, const char **, const int"); -} - -document_t::~document_t() -{ - TRACE_DTOR(xml::document_t); - if (top && top != &stub) - delete top; -} - -void document_t::set_top(node_t * _top) -{ - if (top && top != &stub) - delete top; - top = _top; -} - -int document_t::register_name(const string& name) -{ - int index = lookup_name_id(name); - if (index != -1) - return index; - - names.push_back(name); - index = names.size() - 1; - - DEBUG_("xml.lookup", this << " Inserting name: " << names.back()); - - std::pair result = - names_index.insert(names_pair(names.back(), index)); - assert(result.second); - - return index + 1000; -} - -int document_t::lookup_name_id(const string& name) const -{ - int id; - if ((id = lookup_builtin_id(name)) != -1) - return id; - - DEBUG_("xml.lookup", this << " Finding name: " << name); - - names_map::const_iterator i = names_index.find(name); - if (i != names_index.end()) - return (*i).second + 1000; - - return -1; -} - -int document_t::lookup_builtin_id(const string& name) -{ - int first = 0; - int last = (int)ledger_builtins_size; - - while (first <= last) { - int mid = (first + last) / 2; // compute mid point. - - int result; - if ((result = (int)name[0] - (int)ledger_builtins[mid][0]) == 0) - result = std::strcmp(name.c_str(), ledger_builtins[mid]); - - 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 mid + 10; - } - - return -1; -} - -const char * document_t::lookup_name(int id) const -{ - if (id < 1000) { - switch (id) { - case CURRENT: - return "CURRENT"; - case PARENT: - return "PARENT"; - case ROOT: - return "ROOT"; - case ALL: - return "ALL"; - default: - assert(id >= 10); - return ledger_builtins[id - 10]; - } - } else { - return names[id - 1000].c_str(); - } -} - -void document_t::write(std::ostream& out) const -{ - if (top) { - out << "\n"; - top->write(out); - } -} - -#ifndef THREADSAFE -document_t * node_t::document; -#endif - -node_t::node_t(document_t * _document, parent_node_t * _parent, - unsigned int _flags) - : name_id(0), parent(_parent), next(NULL), prev(NULL), - flags(_flags), attrs(NULL) -{ - TRACE_CTOR(node_t, "document_t *, node_t *"); - document = _document; - if (document && ! document->top) - document->set_top(this); - if (parent) - parent->add_child(this); -} - -void node_t::extract() -{ - if (prev) - prev->next = next; - - if (parent) { - if (parent->_children == this) - parent->_children = next; - - if (parent->_last_child == this) - parent->_last_child = prev; - - parent = NULL; - } - - if (next) - next->prev = prev; - - next = NULL; - prev = NULL; -} - -const char * node_t::name() const -{ - return document->lookup_name(name_id); -} - -int node_t::set_name(const char * _name) -{ - name_id = document->register_name(_name); - return name_id; -} - -node_t * node_t::lookup_child(const char * _name) const -{ - int id = document->lookup_name_id(_name); - return lookup_child(id); -} - -node_t * node_t::lookup_child(const string& _name) const -{ - int id = document->lookup_name_id(_name); - return lookup_child(id); -} - -void parent_node_t::clear() -{ - node_t * child = _children; - while (child) { - node_t * tnext = child->next; - delete child; - child = tnext; - } -} - -void parent_node_t::add_child(node_t * node) -{ - // It is important that this node is not called before children(), - // otherwise, this node will not get auto-populated. - if (_children == NULL) { - assert(_last_child == NULL); - _children = node; - node->prev = NULL; - } else { - assert(_last_child != NULL); - _last_child->next = node; - node->prev = _last_child; - } - - node->parent = this; - - while (node->next) { - node_t * next_node = node->next; - assert(next_node->prev == node); - next_node->parent = this; - node = next_node; - } - - _last_child = node; -} - -void parent_node_t::write(std::ostream& out, int depth) const -{ - for (int i = 0; i < depth; i++) out << " "; - out << '<' << name() << ">\n"; - - for (node_t * child = children(); child; child = child->next) - child->write(out, depth + 1); - - for (int i = 0; i < depth; i++) out << " "; - out << "\n"; -} - -void terminal_node_t::write(std::ostream& out, int depth) const -{ - for (int i = 0; i < depth; i++) out << " "; - - if (data.empty()) { - out << '<' << name() << " />\n"; - } else { - out << '<' << name() << ">" - << text() - << "\n"; - } -} - -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - -template -inline T * create_node(parser_t * parser) -{ - T * node = new T(parser->document, parser->node_stack.empty() ? - NULL : parser->node_stack.front()); - - node->set_name(parser->pending); - node->attrs = parser->pending_attrs; - - parser->pending = NULL; - parser->pending_attrs = NULL; - - return node; -} - -static void startElement(void *userData, const char *name, const char **attrs) -{ - parser_t * parser = static_cast(userData); - - DEBUG_("xml.parse", "startElement(" << name << ")"); - - if (parser->pending) { - parent_node_t * node = create_node(parser); - if (parser->node_stack.empty()) - parser->document->top = node; - parser->node_stack.push_front(node); - } - - parser->pending = name; - - if (attrs) { - for (const char ** p = attrs; *p; p += 2) { - if (! parser->pending_attrs) - parser->pending_attrs = new node_t::attrs_map; - - std::pair result - = parser->pending_attrs->insert(node_t::attrs_pair(*p, *(p + 1))); - assert(result.second); - } - } -} - -static void endElement(void *userData, const char *name) -{ - parser_t * parser = static_cast(userData); - - DEBUG_("xml.parse", "endElement(" << name << ")"); - - if (parser->pending) { - terminal_node_t * node = create_node(parser); - if (parser->node_stack.empty()) { - parser->document->top = node; - return; - } - } - else if (! parser->handled_data) { - assert(! parser->node_stack.empty()); - parser->node_stack.pop_front(); - } - else { - parser->handled_data = false; - } -} - -static void dataHandler(void *userData, const char *s, int len) -{ - parser_t * parser = static_cast(userData); - - DEBUG_("xml.parse", "dataHandler(" << string(s, len) << ")"); - - bool all_whitespace = true; - for (int i = 0; i < len; i++) { - if (! std::isspace(s[i])) { - all_whitespace = false; - break; - } - } - - // jww (2006-09-28): I currently do not support text nodes within a - // node that has children. - - if (! all_whitespace) { - terminal_node_t * node = create_node(parser); - - node->set_text(string(s, len)); - parser->handled_data = true; - - if (parser->node_stack.empty()) { - parser->document->top = node; - return; - } - } -} - -bool parser_t::test(std::istream& in) const -{ - char buf[80]; - - in.getline(buf, 79); - if (std::strncmp(buf, " doc(new document_t); - - document = doc.get(); - - parser = XML_ParserCreate(NULL); - - XML_SetElementHandler(parser, startElement, endElement); - XML_SetCharacterDataHandler(parser, dataHandler); - XML_SetUserData(parser, this); - - char buf[BUFSIZ]; - while (! in.eof()) { - in.getline(buf, BUFSIZ - 1); - std::strcat(buf, "\n"); - bool result; - try { - result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); - } - catch (const std::exception& err) { - //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; - XML_ParserFree(parser); - throw_(parse_exception, err.what()); - } - - if (! have_error.empty()) { - //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; -#if 0 - // jww (2007-04-26): What is this doing?? - parse_exception err(have_error); - std::cerr << "Error: " << err.what() << std::endl; -#endif - have_error = ""; - } - - if (! result) { - //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; - const char * err = XML_ErrorString(XML_GetErrorCode(parser)); - XML_ParserFree(parser); - throw_(parse_exception, err); - } - } - - XML_ParserFree(parser); - - document = NULL; - return doc.release(); -} - -node_t * commodity_node_t::children() const -{ - // jww (2007-04-19): Need to report the commodity and its details - return NULL; -} - -node_t * amount_node_t::children() const -{ - // jww (2007-04-19): Need to report the quantity and commodity - return NULL; -} - -node_t * transaction_node_t::children() const -{ - return parent_node_t::children(); -} - -node_t * transaction_node_t::lookup_child(int _name_id) const -{ - switch (_name_id) { - case document_t::PAYEE: - payee_virtual_node = new terminal_node_t(document); - payee_virtual_node->set_text(transaction->entry->payee); - return payee_virtual_node; - - case document_t::ACCOUNT: - return new account_node_t(document, transaction->account, - const_cast(this)); - } - return NULL; -} - -value_t transaction_node_t::to_value() const -{ - return transaction->amount; -} - -node_t * entry_node_t::children() const -{ - if (! _children) - for (transactions_list::iterator i = entry->transactions.begin(); - i != entry->transactions.end(); - i++) - new transaction_node_t(document, *i, const_cast(this)); - - return parent_node_t::children(); -} - -node_t * entry_node_t::lookup_child(int _name_id) const -{ - switch (_name_id) { - case document_t::CODE: - // jww (2007-04-20): I have to save this and then delete it later - terminal_node_t * code_node = - new terminal_node_t(document, const_cast(this)); - code_node->set_name(document_t::CODE); - code_node->set_text(entry->code); - return code_node; - - case document_t::PAYEE: - // jww (2007-04-20): I have to save this and then delete it later - terminal_node_t * payee_node = - new terminal_node_t(document, const_cast(this)); - payee_node->set_name(document_t::PAYEE); - payee_node->set_text(entry->payee); - return payee_node; - } - return NULL; -} - -node_t * account_node_t::children() const -{ - if (! _children) { - if (! account->name.empty()) { - terminal_node_t * name_node = - new terminal_node_t(document, const_cast(this)); - name_node->set_name(document_t::NAME); - name_node->set_text(account->name); - } - - if (! account->note.empty()) { - terminal_node_t * note_node = - new terminal_node_t(document, const_cast(this)); - note_node->set_name(document_t::NOTE); - note_node->set_text(account->note); - } - - for (accounts_map::iterator i = account->accounts.begin(); - i != account->accounts.end(); - i++) - new account_node_t(document, (*i).second, const_cast(this)); - } - return parent_node_t::children(); -} - -node_t * journal_node_t::children() const -{ - if (! _children) { -#if 0 - account_node_t * master_account = - new account_node_t(document, journal->master, const_cast(this)); -#endif - - parent_node_t * entries = - new parent_node_t(document, const_cast(this)); - entries->set_name(document_t::ENTRIES); - - for (entries_list::iterator i = journal->entries.begin(); - i != journal->entries.end(); - i++) - new entry_node_t(document, *i, const_cast(this)); - } - return parent_node_t::children(); -} - -#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - -void output_xml_string(std::ostream& out, const string& str) -{ - for (const char * s = str.c_str(); *s; s++) { - switch (*s) { - case '<': - out << "<"; - break; - case '>': - out << "&rt;"; - break; - case '&': - out << "&"; - break; - default: - out << *s; - break; - } - } -} - -} // namespace xml -} // namespace ledger diff --git a/xml.h b/xml.h deleted file mode 100644 index 023388d8..00000000 --- a/xml.h +++ /dev/null @@ -1,409 +0,0 @@ -#ifndef _XML_H -#define _XML_H - -#include "value.h" - -namespace ledger { - -class transaction_t; -class entry_t; -class account_t; -class journal_t; - -namespace xml { - -#define XML_NODE_IS_PARENT 0x1 - -DECLARE_EXCEPTION(conversion_exception); - -class parent_node_t; -class document_t; - -class node_t -{ -public: - unsigned int name_id; -#ifdef THREADSAFE - document_t * document; -#else - static document_t * document; -#endif - parent_node_t * parent; - node_t * next; - node_t * prev; - unsigned int flags; - - typedef std::map attrs_map; - typedef std::pair attrs_pair; - - attrs_map * attrs; - - node_t(document_t * _document, parent_node_t * _parent = NULL, - unsigned int _flags = 0); - - virtual ~node_t() { - TRACE_DTOR(node_t); - if (parent) extract(); - if (attrs) delete attrs; - } - - void extract(); // extract this node from its parent's child list - - virtual const char * text() const { - assert(0); - return NULL; - } - - const char * name() const; - int set_name(const char * _name); - int set_name(int _name_id) { - name_id = _name_id; - return name_id; - } - - void set_attr(const char * n, const char * v) { - if (! attrs) - attrs = new attrs_map; - std::pair result = - attrs->insert(attrs_pair(n, v)); - assert(result.second); - } - const char * get_attr(const char * n) { - if (attrs) { - attrs_map::iterator i = attrs->find(n); - if (i != attrs->end()) - return (*i).second.c_str(); - } - return NULL; - } - - node_t * lookup_child(const char * _name) const; - node_t * lookup_child(const string& _name) const; - virtual node_t * lookup_child(int /* _name_id */) const { - return NULL; - } - - virtual value_t to_value() const { - throw_(conversion_exception, "Cannot convert node to a value"); - } - - virtual void write(std::ostream& out, int depth = 0) const = 0; - -private: - node_t(const node_t&); - node_t& operator=(const node_t&); -}; - -class parent_node_t : public node_t -{ -public: - mutable node_t * _children; - mutable node_t * _last_child; - - parent_node_t(document_t * _document, parent_node_t * _parent = NULL) - : node_t(_document, _parent, XML_NODE_IS_PARENT), - _children(NULL), _last_child(NULL) - { - TRACE_CTOR(parent_node_t, "document_t *, parent_node_t *"); - } - virtual ~parent_node_t() { - TRACE_DTOR(parent_node_t); - if (_children) clear(); - } - - virtual void clear(); // clear out all child nodes - virtual node_t * children() const { - return _children; - } - virtual node_t * last_child() { - if (! _children) - children(); - return _last_child; - } - virtual void add_child(node_t * node); - - void write(std::ostream& out, int depth = 0) const; - -private: - parent_node_t(const parent_node_t&); - parent_node_t& operator=(const parent_node_t&); -}; - -class terminal_node_t : public node_t -{ - string data; - -public: - terminal_node_t(document_t * _document, parent_node_t * _parent = NULL) - : node_t(_document, _parent) - { - TRACE_CTOR(terminal_node_t, "document_t *, parent_node_t *"); - } - virtual ~terminal_node_t() { - TRACE_DTOR(terminal_node_t); - } - - virtual const char * text() const { - return data.c_str(); - } - virtual void set_text(const char * _data) { - data = _data; - } - virtual void set_text(const string& _data) { - data = _data; - } - - virtual value_t to_value() const { - return text(); - } - - void write(std::ostream& out, int depth = 0) const; - -private: - terminal_node_t(const node_t&); - terminal_node_t& operator=(const node_t&); -}; - -class document_t -{ - static const char * ledger_builtins[]; - static const std::size_t ledger_builtins_size; - -public: - enum ledger_builtins_t { - ACCOUNT = 10, - ACCOUNT_PATH, - AMOUNT, - CODE, - COMMODITY, - ENTRIES, - ENTRY, - JOURNAL, - NAME, - NOTE, - PAYEE, - TRANSACTION - }; - -private: - typedef std::vector names_array; - - names_array names; - - typedef std::map names_map; - typedef std::pair names_pair; - - names_map names_index; - - terminal_node_t stub; - - public: - node_t * top; - - // Ids 0-9 are reserved. 10-999 are for "builtin" names. 1000+ are - // for dynamically registered names. - enum special_names_t { - CURRENT, PARENT, ROOT, ALL - }; - - document_t(node_t * _top = NULL); - ~document_t(); - - void set_top(node_t * _top); - - int register_name(const string& name); - int lookup_name_id(const string& name) const; - static int lookup_builtin_id(const string& name); - const char * lookup_name(int id) const; - - void write(std::ostream& out) const; -}; - -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - -class parser_t -{ - public: - document_t * document; - XML_Parser parser; - string have_error; - const char * pending; - node_t::attrs_map * pending_attrs; - bool handled_data; - - std::list node_stack; - - parser_t() : document(NULL), pending(NULL), pending_attrs(NULL), - handled_data(false) {} - virtual ~parser_t() {} - - virtual bool test(std::istream& in) const; - virtual document_t * parse(std::istream& in); -}; - -DECLARE_EXCEPTION(parse_exception); - -#endif - -class commodity_node_t : public parent_node_t -{ -public: - commodity_t * commodity; - - commodity_node_t(document_t * _document, - commodity_t * _commodity, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), commodity(_commodity) { - TRACE_CTOR(commodity_node_t, "document_t *, commodity_t *, parent_node_t *"); - set_name(document_t::COMMODITY); - } - virtual ~commodity_node_t() { - TRACE_DTOR(commodity_node_t); - } - - virtual node_t * children() const; -}; - -class amount_node_t : public parent_node_t -{ -public: - amount_t * amount; - - amount_node_t(document_t * _document, - amount_t * _amount, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), amount(_amount) { - TRACE_CTOR(amount_node_t, "document_t *, amount_t *, parent_node_t *"); - set_name(document_t::AMOUNT); - } - virtual ~amount_node_t() { - TRACE_DTOR(amount_node_t); - } - - virtual node_t * children() const; - - virtual value_t to_value() const { - return *amount; - } -}; - -class transaction_node_t : public parent_node_t -{ - mutable terminal_node_t * payee_virtual_node; - -public: - transaction_t * transaction; - - transaction_node_t(document_t * _document, - transaction_t * _transaction, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), payee_virtual_node(NULL), - transaction(_transaction) { - TRACE_CTOR(transaction_node_t, "document_t *, transaction_t *, parent_node_t *"); - set_name(document_t::TRANSACTION); - } - virtual ~transaction_node_t() { - TRACE_DTOR(transaction_node_t); - if (payee_virtual_node) - delete payee_virtual_node; - } - - virtual node_t * children() const; - virtual node_t * lookup_child(int _name_id) const; - virtual value_t to_value() const; -}; - -class entry_node_t : public parent_node_t -{ - entry_t * entry; - -public: - entry_node_t(document_t * _document, entry_t * _entry, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), entry(_entry) { - TRACE_CTOR(entry_node_t, "document_t *, entry_t *, parent_node_t *"); - set_name(document_t::ENTRY); - } - virtual ~entry_node_t() { - TRACE_DTOR(entry_node_t); - } - - virtual node_t * children() const; - virtual node_t * lookup_child(int _name_id) const; - - friend class transaction_node_t; -}; - -class account_node_t : public parent_node_t -{ - account_t * account; - -public: - account_node_t(document_t * _document, account_t * _account, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), account(_account) { - TRACE_CTOR(account_node_t, "document_t *, account_t *, parent_node_t *"); - set_name(document_t::ACCOUNT); - } - virtual ~account_node_t() { - TRACE_DTOR(account_node_t); - } - - virtual node_t * children() const; -}; - -class journal_node_t : public parent_node_t -{ - journal_t * journal; - -public: - journal_node_t(document_t * _document, journal_t * _journal, - parent_node_t * _parent = NULL) - : parent_node_t(_document, _parent), journal(_journal) { - TRACE_CTOR(journal_node_t, "document_t *, journal_t *, parent_node_t *"); - set_name(document_t::JOURNAL); - } - virtual ~journal_node_t() { - TRACE_DTOR(journal_node_t); - } - - virtual node_t * children() const; - - friend class transaction_node_t; -}; - -template -inline parent_node_t * wrap_node(document_t * doc, T * item, - void * parent_node = NULL) { - assert(0); - return NULL; -} - -template <> -inline parent_node_t * wrap_node(document_t * doc, transaction_t * xact, - void * parent_node) { - return new transaction_node_t(doc, xact, (parent_node_t *)parent_node); -} - -template <> -inline parent_node_t * wrap_node(document_t * doc, entry_t * entry, - void * parent_node) { - return new entry_node_t(doc, entry, (parent_node_t *)parent_node); -} - -template <> -inline parent_node_t * wrap_node(document_t * doc, account_t * account, - void * parent_node) { - return new account_node_t(doc, account, (parent_node_t *)parent_node); -} - -template <> -inline parent_node_t * wrap_node(document_t * doc, journal_t * journal, - void * parent_node) { - return new journal_node_t(doc, journal, (parent_node_t *)parent_node); -} - -} // namespace xml -} // namespace ledger - -#endif // _XML_H diff --git a/xmlparse.cc b/xmlparse.cc deleted file mode 100644 index 35d26e5a..00000000 --- a/xmlparse.cc +++ /dev/null @@ -1,465 +0,0 @@ -#include "xmlparse.h" -#include "journal.h" - -namespace ledger { - -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - -static XML_Parser current_parser; -static unsigned int count; - -static journal_t * curr_journal; -static entry_t * curr_entry; -static commodity_t * curr_comm; -static string comm_flags; - -static transaction_t::state_t curr_state; - -static string data; -static bool ignore; -static string have_error; - -static void startElement(void *userData, const char *name, const char **attrs) -{ - if (ignore) - return; - - if (std::strcmp(name, "entry") == 0) { - assert(! curr_entry); - curr_entry = new entry_t; - curr_state = transaction_t::UNCLEARED; - } - else if (std::strcmp(name, "transaction") == 0) { - assert(curr_entry); - curr_entry->add_transaction(new transaction_t); - if (curr_state != transaction_t::UNCLEARED) - curr_entry->transactions.back()->state = curr_state; - } - else if (std::strcmp(name, "commodity") == 0) { - if (string(attrs[0]) == "flags") - comm_flags = attrs[1]; - } - else if (std::strcmp(name, "total") == 0) { - ignore = true; - } -} - -static void endElement(void *userData, const char *name) -{ - if (ignore) { - if (std::strcmp(name, "total") == 0) - ignore = false; - return; - } - - if (std::strcmp(name, "entry") == 0) { - assert(curr_entry); - if (curr_journal->add_entry(curr_entry)) { - count++; - } else { - account_t * acct = curr_journal->find_account(""); - curr_entry->add_transaction(new transaction_t(acct)); - if (curr_journal->add_entry(curr_entry)) { - count++; - } else { - delete curr_entry; - have_error = "Entry cannot be balanced"; - } - } - curr_entry = NULL; - } - else if (std::strcmp(name, "en:date") == 0) { - curr_entry->_date = parse_datetime(data); - } - else if (std::strcmp(name, "en:date_eff") == 0) { - curr_entry->_date_eff = parse_datetime(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; - } - else if (std::strcmp(name, "en:pending") == 0) { - curr_state = transaction_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); - } - else if (std::strcmp(name, "tr:cleared") == 0) { - curr_entry->transactions.back()->state = transaction_t::CLEARED; - } - else if (std::strcmp(name, "tr:pending") == 0) { - curr_entry->transactions.back()->state = transaction_t::PENDING; - } - else if (std::strcmp(name, "tr:virtual") == 0) { - curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; - } - else if (std::strcmp(name, "tr:generated") == 0) { - curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; - } - else if (std::strcmp(name, "symbol") == 0) { - assert(! curr_comm); - curr_comm = commodity_t::find_or_create(data); - assert(curr_comm); - curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); - if (! comm_flags.empty()) { - 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; - case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; - case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; - } - } - } - } -#if 0 - // jww (2006-03-02): !!! - else if (std::strcmp(name, "price") == 0) { - assert(curr_comm); - amount_t * price = new amount_t(data); - std::ostringstream symstr; - symstr << curr_comm->symbol << " {" << *price << "}"; - commodity_t * priced_comm = - commodity_t::find_commodity(symstr.str(), true); - priced_comm->price = price; - priced_comm->base = curr_comm; - curr_comm = priced_comm; - } -#endif - else if (std::strcmp(name, "quantity") == 0) { - curr_entry->transactions.back()->amount.parse(data); - if (curr_comm) { - 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_comm = NULL; - } - } - else if (std::strcmp(name, "tr:amount") == 0) { - curr_comm = NULL; - } -} - -static void dataHandler(void *userData, const char *s, int len) -{ - if (! ignore) - data = string(s, len); -} - -bool xml_parser_t::test(std::istream& in) const -{ - char buf[80]; - - in.getline(buf, 79); - if (std::strncmp(buf, "\n"; - - commodity_t& c = amount.commodity(); - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "\n"; - for (int i = 0; i < depth + 4; i++) out << ' '; -#if 0 - // jww (2006-03-02): !!! - if (c.price) { - out << "" << c.base->symbol << "\n"; - for (int i = 0; i < depth + 4; i++) out << ' '; - out << "\n"; - xml_write_amount(out, *c.price, depth + 6); - for (int i = 0; i < depth + 4; i++) out << ' '; - out << "\n"; - } else { - out << "" << c.symbol << "\n"; - } -#endif - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "\n"; - - for (int i = 0; i < depth + 2; i++) out << ' '; - out << ""; - out << amount.quantity_string() << "\n"; - - for (int i = 0; i < depth; i++) out << ' '; - out << "\n"; -} - -void xml_write_value(std::ostream& out, const value_t& value, - const int depth = 0) -{ - balance_t * bal = NULL; - - for (int i = 0; i < depth; i++) out << ' '; - out << "\n"; - - switch (value.type) { - case value_t::BOOLEAN: - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "" << *((bool *) value.data) << "\n"; - break; - - case value_t::INTEGER: - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "" << *((long *) value.data) << "\n"; - break; - - case value_t::AMOUNT: - xml_write_amount(out, *((amount_t *) value.data), depth + 2); - 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; - - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "\n"; - - for (amounts_map::const_iterator i = bal->amounts.begin(); - i != bal->amounts.end(); - i++) - xml_write_amount(out, (*i).second, depth + 4); - - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "\n"; - break; - - default: - assert(0); - break; - } - - for (int i = 0; i < depth; i++) out << ' '; - out << "\n"; -} - -void output_xml_string(std::ostream& out, const string& str) -{ - for (const char * s = str.c_str(); *s; s++) { - switch (*s) { - case '<': - out << "<"; - break; - case '>': - out << "&rt;"; - break; - case '&': - out << "&"; - break; - default: - out << *s; - break; - } - } -} - -void format_xml_entries::format_last_entry() -{ - output_stream << " \n" - << " " << last_entry->_date.to_string("%Y/%m/%d") - << "\n"; - - if (last_entry->_date_eff) - output_stream << " " - << last_entry->_date_eff.to_string("%Y/%m/%d") - << "\n"; - - if (! last_entry->code.empty()) { - output_stream << " "; - output_xml_string(output_stream, last_entry->code); - output_stream << "\n"; - } - - if (! last_entry->payee.empty()) { - output_stream << " "; - output_xml_string(output_stream, last_entry->payee); - output_stream << "\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) { - if (first) { - output_stream << " \n"; - first = false; - } - - output_stream << " \n"; - - if ((*i)->_date) - output_stream << " " - << (*i)->_date.to_string("%Y/%m/%d") - << "\n"; - - if ((*i)->_date_eff) - output_stream << " " - << (*i)->_date_eff.to_string("%Y/%m/%d") - << "\n"; - - if ((*i)->state == transaction_t::CLEARED) - output_stream << " \n"; - else if ((*i)->state == transaction_t::PENDING) - output_stream << " \n"; - - if ((*i)->flags & TRANSACTION_VIRTUAL) - output_stream << " \n"; - if ((*i)->flags & TRANSACTION_AUTO) - output_stream << " \n"; - - if ((*i)->account) { - string name = (*i)->account->fullname(); - if (name == "") - name = "[TOTAL]"; - else if (name == "") - name = "[UNKNOWN]"; - - output_stream << " "; - output_xml_string(output_stream, name); - output_stream << "\n"; - } - - output_stream << " \n"; - if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND) - xml_write_value(output_stream, - transaction_xdata_(**i).value, 10); - else - xml_write_value(output_stream, value_t((*i)->amount), 10); - output_stream << " \n"; - - if ((*i)->cost) { - output_stream << " \n"; - xml_write_value(output_stream, value_t(*(*i)->cost), 10); - output_stream << " \n"; - } - - if (! (*i)->note.empty()) { - output_stream << " "; - output_xml_string(output_stream, (*i)->note); - output_stream << "\n"; - } - - if (show_totals) { - output_stream << " \n"; - xml_write_value(output_stream, transaction_xdata_(**i).total, 10); - output_stream << " \n"; - } - - output_stream << " \n"; - - transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; - } - } - - if (! first) - output_stream << " \n"; - - output_stream << " \n"; -} -#endif - -} // namespace ledger diff --git a/xmlparse.h b/xmlparse.h deleted file mode 100644 index f8a77106..00000000 --- a/xmlparse.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef _XMLPARSE_H -#define _XMLPARSE_H - -#include "parser.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, - journal_t * journal, - account_t * master = NULL, - const string * original_file = NULL); -}; - -#endif - -} // namespace ledger - -#endif // _XMLPARSE_H diff --git a/xpath.cc b/xpath.cc deleted file mode 100644 index 574a6ac8..00000000 --- a/xpath.cc +++ /dev/null @@ -1,2419 +0,0 @@ -#include "xpath.h" -#include "parser.h" -#if 0 -#ifdef USE_BOOST_PYTHON -#include "py_eval.h" -#endif -#endif - -namespace ledger { -namespace xml { - -#ifndef THREADSAFE -xpath_t::token_t * xpath_t::lookahead = NULL; -#endif - -void xpath_t::initialize() -{ - lookahead = new xpath_t::token_t; -} - -void xpath_t::shutdown() -{ - delete lookahead; - lookahead = NULL; -} - -void xpath_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 == '.'); - - switch (buf[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; - case 'f': - if (std::strcmp(buf, "false") == 0) { - kind = VALUE; - value = false; - } - break; - 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; - case 't': - if (std::strcmp(buf, "true") == 0) { - kind = VALUE; - value = true; - } - break; - case 'u': - if (std::strcmp(buf, "union") == 0) - kind = KW_UNION; - break; - } - - if (kind == IDENT) - value.set_string(buf); -} - -void xpath_t::token_t::next(std::istream& in, unsigned short flags) -{ - 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; - - if (! (flags & XPATH_PARSE_RELAXED) && - (std::isalpha(c) || c == '_')) { - parse_ident(in); - return; - } - - switch (c) { - case '@': - in.get(c); - kind = AT_SYM; - break; -#if 0 - case '$': - in.get(c); - kind = DOLLAR; - break; -#endif - - case '(': - in.get(c); - kind = LPAREN; - break; - case ')': - in.get(c); - kind = RPAREN; - break; - - case '[': { - in.get(c); - if (flags & XPATH_PARSE_ALLOW_DATE) { - char buf[256]; - READ_INTO_(in, buf, 255, c, length, c != ']'); - if (c != ']') - unexpected(c, ']'); - in.get(c); - length++; - interval_t timespan(buf); - kind = VALUE; - value = timespan.next(); - } else { - kind = LBRACKET; - } - break; - } - - case ']': { - in.get(c); - kind = RBRACKET; - break; - } - - case '"': { - in.get(c); - char buf[4096]; - READ_INTO_(in, buf, 4095, c, length, c != '"'); - if (c != '"') - unexpected(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 != '}') - unexpected(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; - } -#if 0 - else if (c == '~') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = NMATCH; - length = 2; - break; - } -#endif - kind = EXCLAM; - break; - - case '-': - in.get(c); - kind = MINUS; - break; - case '+': - in.get(c); - kind = PLUS; - break; - - case '*': - in.get(c); - if (in.peek() == '*') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = POWER; - length = 2; - break; - } - kind = STAR; - break; - - case '/': - in.get(c); -#if 0 - if (flags & XPATH_PARSE_REGEXP) { - char buf[1024]; - READ_INTO_(in, buf, 1023, c, length, c != '/'); - in.get(c); - if (c != '/') - unexpected(c, '/'); - kind = REGEXP; - value.set_string(buf); - break; - } -#endif - kind = SLASH; - break; - - case '=': - in.get(c); -#if 0 - if (in.peek() == '~') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = MATCH; - length = 2; - break; - } -#endif - 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 = AMPER; - break; - case '|': - in.get(c); - kind = PIPE; - break; - case '?': - in.get(c); - kind = QUESTION; - break; - case ':': - in.get(c); - if (in.peek() == '=') { - in.get(c); - symbol[1] = c; - symbol[2] = '\0'; - kind = ASSIGN; - length = 2; - break; - } - kind = COLON; - break; - case ',': - in.get(c); - kind = COMMA; - break; -#if 0 - case '%': - in.get(c); - kind = PERCENT; - break; -#endif - - case '.': - in.get(c); - c = in.peek(); - if (c == '.') { - in.get(c); - length++; - kind = DOTDOT; - break; - } - else if (! std::isdigit(c)) { - kind = DOT; - break; - } - in.unget(); // put the first '.' back - // fall through... - - default: - if (! (flags & XPATH_PARSE_RELAXED)) { - kind = UNKNOWN; - } else { - 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. - try { - pos = (long)in.tellg(); - - unsigned char parse_flags = 0; - if (flags & XPATH_PARSE_NO_MIGRATE) - parse_flags |= AMOUNT_PARSE_NO_MIGRATE; - if (flags & XPATH_PARSE_NO_REDUCE) - parse_flags |= AMOUNT_PARSE_NO_REDUCE; - - temp.parse(in, parse_flags); - - kind = VALUE; - value = temp; - } - catch (amount_exception& err) { - // If the amount had no commodity, it must be an unambiguous - // variable reference - - // jww (2007-04-19): There must be a more efficient way to do this! - if (std::strcmp(err.what(), "No quantity specified for amount") == 0) { - in.clear(); - in.seekg(pos, std::ios::beg); - - c = in.peek(); - assert(! (std::isdigit(c) || c == '.')); - parse_ident(in); - } else { - throw; - } - } - } - break; - } -} - -void xpath_t::token_t::rewind(std::istream& in) -{ - for (unsigned int i = 0; i < length; i++) - in.unget(); -} - - -void xpath_t::token_t::unexpected() -{ - switch (kind) { - case TOK_EOF: - throw_(parse_exception, "Unexpected end of expression"); - case IDENT: - throw_(parse_exception, "Unexpected symbol '" << value << "'"); - case VALUE: - throw_(parse_exception, "Unexpected value '" << value << "'"); - default: - throw_(parse_exception, "Unexpected operator '" << symbol << "'"); - } -} - -void xpath_t::token_t::unexpected(char c, char wanted) -{ - if ((unsigned char) c == 0xff) { - if (wanted) - throw_(parse_exception, "Missing '" << wanted << "'"); - else - throw_(parse_exception, "Unexpected end"); - } else { - if (wanted) - throw_(parse_exception, "Invalid char '" << c << - "' (wanted '" << wanted << "')"); - else - throw_(parse_exception, "Invalid char '" << c << "'"); - } -} - -xpath_t::op_t * xpath_t::wrap_value(const value_t& val) -{ - xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::VALUE); - temp->valuep = new value_t(val); - return temp; -} - -xpath_t::op_t * xpath_t::wrap_sequence(value_t::sequence_t * val) -{ - if (val->size() == 0) - return wrap_value(false); - else if (val->size() == 1) - return wrap_value(val->front()); - else - return wrap_value(val); -} - -xpath_t::op_t * xpath_t::wrap_functor(functor_t * fobj) -{ - xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::FUNCTOR); - temp->functor = fobj; - return temp; -} - -#if 0 -xpath_t::op_t * xpath_t::wrap_mask(const string& pattern) -{ - xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::MASK); - temp->mask = new mask_t(pattern); - return temp; -} -#endif - -void xpath_t::scope_t::define(const string& name, op_t * def) -{ - DEBUG_("ledger.xpath.syms", "Defining '" << name << "' = " << def); - - std::pair result - = symbols.insert(symbol_pair(name, def)); - if (! result.second) { - symbol_map::iterator i = symbols.find(name); - assert(i != symbols.end()); - (*i).second->release(); - symbols.erase(i); - - std::pair result2 - = symbols.insert(symbol_pair(name, def)); - if (! result2.second) - throw_(compile_exception, - "Redefinition of '" << name << "' in same scope"); - } - def->acquire(); -} - -xpath_t::op_t * -xpath_t::scope_t::lookup(const 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; -} - -void xpath_t::scope_t::define(const string& name, functor_t * def) { - define(name, wrap_functor(def)); -} - -bool xpath_t::function_scope_t::resolve(const string& name, - value_t& result, - scope_t * locals) -{ - switch (name[0]) { - case 'l': - if (name == "last") { - if (sequence) - result = (long)sequence->size(); - else - result = 1L; - return true; - } - break; - - case 'p': - if (name == "position") { - result = (long)index + 1; - return true; - } - break; - - case 't': - if (name == "text") { - if (value->type == value_t::XML_NODE) - result.set_string(value->to_xml_node()->text()); - else - throw_(calc_exception, "Attempt to call text() on a non-node value"); - return true; - } - break; - } - return scope_t::resolve(name, result, locals); -} - -xpath_t::op_t::~op_t() -{ - TRACE_DTOR(xpath_t::op_t); - - DEBUG_("ledger.xpath.memory", "Destroying " << this); - assert(refc == 0); - - switch (kind) { - case VALUE: - assert(! left); - assert(valuep); - delete valuep; - break; - - case NODE_NAME: - case FUNC_NAME: - case ATTR_NAME: - case VAR_NAME: - assert(! left); - assert(name); - delete name; - break; - - case ARG_INDEX: - break; - - case FUNCTOR: - assert(! left); - assert(functor); - delete functor; - break; - -#if 0 - case MASK: - assert(! left); - assert(mask); - delete mask; - break; -#endif - - default: - assert(kind < LAST); - if (left) - left->release(); - if (kind > TERMINALS && right) - right->release(); - break; - } -} - -void xpath_t::op_t::get_value(value_t& result) const -{ - switch (kind) { - case VALUE: - result = *valuep; - break; - case ARG_INDEX: - result = (long)arg_index; - break; - default: - throw_(calc_exception, - "Cannot determine value of expression symbol '" << *this << "'"); - } -} - -xpath_t::op_t * -xpath_t::parse_value_term(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node; - - token_t& tok = next_token(in, tflags); - - switch (tok.kind) { - case token_t::VALUE: - node.reset(new op_t(op_t::VALUE)); - node->valuep = new value_t(tok.value); - break; - - case token_t::IDENT: { -#if 0 -#ifdef USE_BOOST_PYTHON - if (tok.value->to_string() == "lambda") // special - try { - char c, buf[4096]; - - std::strcpy(buf, "lambda "); - READ_INTO(in, &buf[7], 4000, c, true); - - op_t * eval = new op_t(op_t::O_EVAL); - op_t * lambda = new op_t(op_t::FUNCTOR); - lambda->functor = new python_functor_t(python_eval(buf)); - eval->set_left(lambda); - op_t * sym = new op_t(op_t::SYMBOL); - sym->name = new string("__ptr"); - eval->set_right(sym); - - node.reset(eval); - - goto done; - } - catch(const boost::python::error_already_set&) { - throw_(parse_exception, "Error parsing lambda expression"); - } -#endif /* USE_BOOST_PYTHON */ -#endif - - string ident = tok.value.to_string(); - int id = -1; - if (std::isdigit(ident[0])) { - node.reset(new op_t(op_t::ARG_INDEX)); - node->arg_index = std::atol(ident.c_str()); - } - else if ((id = document_t::lookup_builtin_id(ident)) != -1) { - node.reset(new op_t(op_t::NODE_ID)); - node->name_id = id; - } - else { - node.reset(new op_t(op_t::NODE_NAME)); - node->name = new string(ident); - } - - // An identifier followed by ( represents a function call - tok = next_token(in, tflags); - if (tok.kind == token_t::LPAREN) { - node->kind = op_t::FUNC_NAME; - - std::auto_ptr call_node; - call_node.reset(new op_t(op_t::O_EVAL)); - call_node->set_left(node.release()); - call_node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); - - tok = next_token(in, tflags); - if (tok.kind != token_t::RPAREN) - tok.unexpected(); // jww (2006-09-09): wanted ) - - node.reset(call_node.release()); - } else { - push_token(tok); - } - break; - } - - case token_t::AT_SYM: - tok = next_token(in, tflags); - if (tok.kind != token_t::IDENT) - throw_(parse_exception, "@ symbol must be followed by attribute name"); - - node.reset(new op_t(op_t::ATTR_NAME)); - node->name = new string(tok.value.to_string()); - break; - -#if 0 - case token_t::DOLLAR: - tok = next_token(in, tflags); - if (tok.kind != token_t::IDENT) - throw parse_error("$ symbol must be followed by variable name"); - - node.reset(new op_t(op_t::VAR_NAME)); - node->name = new string(tok.value.to_string()); - break; -#endif - - case token_t::DOT: - node.reset(new op_t(op_t::NODE_ID)); - node->name_id = document_t::CURRENT; - break; - case token_t::DOTDOT: - node.reset(new op_t(op_t::NODE_ID)); - node->name_id = document_t::PARENT; - break; - case token_t::SLASH: - node.reset(new op_t(op_t::NODE_ID)); - node->name_id = document_t::ROOT; - push_token(); - break; - case token_t::STAR: - node.reset(new op_t(op_t::NODE_ID)); - node->name_id = document_t::ALL; - break; - - case token_t::LPAREN: - node.reset(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); - if (! node.get()) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - tok = next_token(in, tflags); - if (tok.kind != token_t::RPAREN) - tok.unexpected(); // jww (2006-09-09): wanted ) - break; - -#if 0 - case token_t::REGEXP: - node.reset(wrap_mask(tok.value.to_string())); - break; -#endif - - default: - push_token(tok); - break; - } - -#if 0 -#ifdef USE_BOOST_PYTHON - done: -#endif -#endif - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_predicate_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_value_term(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - while (tok.kind == token_t::LBRACKET) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(op_t::O_PRED)); - node->set_left(prev.release()); - node->set_right(parse_value_expr(in, tflags | XPATH_PARSE_PARTIAL)); - if (! node->right) - throw_(parse_exception, "[ operator not followed by valid expression"); - - tok = next_token(in, tflags); - if (tok.kind != token_t::RBRACKET) - tok.unexpected(); // jww (2006-09-09): wanted ] - - tok = next_token(in, tflags); - } - - push_token(tok); - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_path_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_predicate_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - while (tok.kind == token_t::SLASH) { - std::auto_ptr prev(node.release()); - - tok = next_token(in, tflags); - node.reset(new op_t(tok.kind == token_t::SLASH ? - op_t::O_RFIND : op_t::O_FIND)); - if (tok.kind != token_t::SLASH) - push_token(tok); - - node->set_left(prev.release()); - node->set_right(parse_predicate_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, "/ operator not followed by a valid term"); - - tok = next_token(in, tflags); - } - - push_token(tok); - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_unary_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node; - - token_t& tok = next_token(in, tflags); - - switch (tok.kind) { - case token_t::EXCLAM: { - std::auto_ptr texpr(parse_path_expr(in, tflags)); - if (! texpr.get()) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - // A very quick optimization - if (texpr->kind == op_t::VALUE) { - *texpr->valuep = ! *texpr->valuep; - node.reset(texpr.release()); - } else { - node.reset(new op_t(op_t::O_NOT)); - node->set_left(texpr.release()); - } - break; - } - - case token_t::MINUS: { - std::auto_ptr texpr(parse_path_expr(in, tflags)); - if (! texpr.get()) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - // A very quick optimization - if (texpr->kind == op_t::VALUE) { - texpr->valuep->in_place_negate(); - node.reset(texpr.release()); - } else { - node.reset(new op_t(op_t::O_NEG)); - node->set_left(texpr.release()); - } - break; - } - -#if 0 - case token_t::PERCENT: { - std::auto_ptr texpr(parse_path_expr(in, tflags)); - if (! texpr.get()) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - // A very quick optimization - if (texpr->kind == op_t::VALUE) { - static value_t perc("100.0%"); - *texpr->valuep = perc * *texpr->valuep; - node.reset(texpr.release()); - } else { - node.reset(new op_t(op_t::O_PERC)); - node->set_left(texpr.release()); - } - break; - } -#endif - - default: - push_token(tok); - node.reset(parse_path_expr(in, tflags)); - break; - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_union_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_unary_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::PIPE || tok.kind == token_t::KW_UNION) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(op_t::O_UNION)); - node->set_left(prev.release()); - node->set_right(parse_union_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_mul_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_union_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(tok.kind == token_t::STAR ? - op_t::O_MUL : op_t::O_DIV)); - node->set_left(prev.release()); - node->set_right(parse_mul_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - - tok = next_token(in, tflags); - } - push_token(tok); - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_add_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_mul_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::PLUS || - tok.kind == token_t::MINUS) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(tok.kind == token_t::PLUS ? - op_t::O_ADD : op_t::O_SUB)); - node->set_left(prev.release()); - node->set_right(parse_add_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - - tok = next_token(in, tflags); - } - push_token(tok); - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_logic_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_add_expr(in, tflags)); - - if (node.get()) { - op_t::kind_t kind = op_t::LAST; - - unsigned short _flags = tflags; - - token_t& tok = next_token(in, tflags); - switch (tok.kind) { - case token_t::ASSIGN: - kind = op_t::O_DEFINE; - break; - case token_t::EQUAL: - kind = op_t::O_EQ; - break; - case token_t::NEQUAL: - kind = op_t::O_NEQ; - break; -#if 0 - case token_t::MATCH: - kind = op_t::O_MATCH; - _flags |= XPATH_PARSE_REGEXP; - break; - case token_t::NMATCH: - kind = op_t::O_NMATCH; - _flags |= XPATH_PARSE_REGEXP; - break; -#endif - 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) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(kind)); - node->set_left(prev.release()); - if (kind == op_t::O_DEFINE) - node->set_right(parse_querycolon_expr(in, tflags)); - else - node->set_right(parse_add_expr(in, _flags)); - - if (! node->right) { - if (tok.kind == token_t::PLUS) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - else - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - } - } - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_and_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_logic_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::KW_AND) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(op_t::O_AND)); - node->set_left(prev.release()); - node->set_right(parse_and_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_or_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_and_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::KW_OR) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(op_t::O_OR)); - node->set_left(prev.release()); - node->set_right(parse_or_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_querycolon_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_or_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::QUESTION) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(op_t::O_QUES)); - node->set_left(prev.release()); - node->set_right(new op_t(op_t::O_COLON)); - node->right->set_left(parse_querycolon_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - tok = next_token(in, tflags); - if (tok.kind != token_t::COLON) - tok.unexpected(); // jww (2006-09-09): wanted : - node->right->set_right(parse_querycolon_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - } else { - push_token(tok); - } - } - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_value_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_querycolon_expr(in, tflags)); - - if (node.get()) { - token_t& tok = next_token(in, tflags); - if (tok.kind == token_t::COMMA) { - std::auto_ptr prev(node.release()); - node.reset(new op_t(op_t::O_COMMA)); - node->set_left(prev.release()); - node->set_right(parse_value_expr(in, tflags)); - if (! node->right) - throw_(parse_exception, - tok.symbol << " operator not followed by argument"); - tok = next_token(in, tflags); - } - - if (tok.kind != token_t::TOK_EOF) { - if (tflags & XPATH_PARSE_PARTIAL) - push_token(tok); - else - tok.unexpected(); - } - } - else if (! (tflags & XPATH_PARSE_PARTIAL)) { - throw_(parse_exception, "Failed to parse value expression"); - } - - return node.release(); -} - -xpath_t::op_t * -xpath_t::parse_expr(std::istream& in, unsigned short tflags) const -{ - std::auto_ptr node(parse_value_expr(in, tflags)); - - if (use_lookahead) { - use_lookahead = false; -#ifdef THREADSAFE - lookahead.rewind(in); -#else - lookahead->rewind(in); -#endif - } -#ifdef THREADSAFE - lookahead.clear(); -#else - lookahead->clear(); -#endif - - return node.release(); -} - -xpath_t::op_t * -xpath_t::op_t::new_node(kind_t kind, op_t * left, op_t * right) -{ - std::auto_ptr node(new op_t(kind)); - if (left) - node->set_left(left); - if (right) - node->set_right(right); - return node.release(); -} - -xpath_t::op_t * -xpath_t::op_t::copy(op_t * tleft, op_t * tright) const -{ - std::auto_ptr node(new op_t(kind)); - if (tleft) - node->set_left(tleft); - if (tright) - node->set_right(tright); - return node.release(); -} - -void xpath_t::op_t::find_values(value_t * context, scope_t * scope, - value_t::sequence_t& result_seq, - bool recursive) -{ - xpath_t expr(compile(context, scope, true)); - - if (expr->kind == VALUE) - append_value(*expr->valuep, result_seq); - - if (recursive) { - if (context->type == value_t::XML_NODE) { - node_t * ptr = context->to_xml_node(); - if (ptr->flags & XML_NODE_IS_PARENT) { - parent_node_t * parent = static_cast(ptr); - for (node_t * node = parent->children(); - node; - node = node->next) { - value_t temp(node); - find_values(&temp, scope, result_seq, recursive); - } - } - } else { - throw_(calc_exception, "Recursive path selection on a non-node value"); - } - } -} - -bool xpath_t::op_t::test_value(value_t * context, scope_t * scope, - int index) -{ - xpath_t expr(compile(context, scope, true)); - - if (expr->kind != VALUE) - throw_(calc_exception, "Predicate expression does not yield a constant value"); - - switch (expr->valuep->type) { - case value_t::INTEGER: - case value_t::AMOUNT: - return *expr->valuep == (long)index + 1; - - default: - return expr->valuep->to_boolean(); - } -} - -xpath_t::op_t * xpath_t::op_t::defer_sequence(value_t::sequence_t& result_seq) -{ - // If not all of the elements were constants, transform the result - // into an expression sequence using O_COMMA. - - assert(! result_seq.empty()); - - if (result_seq.size() == 1) - return wrap_value(result_seq.front())->acquire(); - - value_t::sequence_t::iterator i = result_seq.begin(); - - std::auto_ptr lit_seq(new op_t(O_COMMA)); - - lit_seq->set_left(wrap_value(*i++)); - op_t ** opp = &lit_seq->right; - - for (; i != result_seq.end(); i++) { - if (*opp) { - op_t * val = *opp; - *opp = new op_t(O_COMMA); - (*opp)->set_left(val); - opp = &(*opp)->right; - } - - if ((*i).type != value_t::POINTER) - *opp = wrap_value(*i)->acquire(); - else - *opp = static_cast((*i).to_pointer()); - } - - return lit_seq.release(); -} - -void xpath_t::op_t::append_value(value_t& val, - value_t::sequence_t& result_seq) -{ - if (val.type == value_t::SEQUENCE) { - value_t::sequence_t * subseq = val.to_sequence(); - for (value_t::sequence_t::iterator i = subseq->begin(); - i != subseq->end(); - i++) - result_seq.push_back(*i); - } else { - result_seq.push_back(val); - } -} - -xpath_t::op_t * xpath_t::op_t::compile(value_t * context, scope_t * scope, - bool resolve) -{ -#if 0 - try { -#endif - switch (kind) { - case VALUE: - return acquire(); - - case NODE_ID: - switch (name_id) { - case document_t::CURRENT: - return wrap_value(context)->acquire(); - - case document_t::PARENT: - if (context->type != value_t::XML_NODE) - throw_(compile_exception, "Referencing parent node from a non-node value"); - else if (context->to_xml_node()->parent) - return wrap_value(context->to_xml_node()->parent)->acquire(); - else - throw_(compile_exception, "Referencing parent node from the root node"); - - case document_t::ROOT: - if (context->type != value_t::XML_NODE) - throw_(compile_exception, "Referencing root node from a non-node value"); - else - return wrap_value(context->to_xml_node()->document->top)->acquire(); - - case document_t::ALL: { - if (context->type != value_t::XML_NODE) - throw_(compile_exception, "Referencing child nodes from a non-node value"); - - node_t * ptr = context->to_xml_node(); - if (! (ptr->flags & XML_NODE_IS_PARENT)) - throw_(compile_exception, "Request for child nodes of a leaf node"); - - parent_node_t * parent = static_cast(ptr); - - value_t::sequence_t * nodes = new value_t::sequence_t; - for (node_t * node = parent->children(); node; node = node->next) - nodes->push_back(node); - - return wrap_value(nodes)->acquire(); - } - - default: - break; // pass down to the NODE_NAME case - } - // fall through... - - case NODE_NAME: - if (context->type == value_t::XML_NODE) { - node_t * ptr = context->to_xml_node(); - if (resolve) { - // First, look up the symbol as a node name within the current - // context. If any exist, then return the set of names. - - std::auto_ptr nodes(new value_t::sequence_t); - - if (ptr->flags & XML_NODE_IS_PARENT) { - parent_node_t * parent = static_cast(ptr); - for (node_t * node = parent->children(); - node; - node = node->next) { - if ((kind == NODE_NAME && - std::strcmp(name->c_str(), node->name()) == 0) || - (kind == NODE_ID && name_id == node->name_id)) - nodes->push_back(node); - } - } - return wrap_value(nodes.release())->acquire(); - } else { - assert(ptr); - int id = ptr->document->lookup_name_id(*name); - if (id != -1) { - op_t * node = new_node(NODE_ID); - node->name_id = id; - return node->acquire(); - } - } - } - return acquire(); - - case ATTR_NAME: { - // jww (2006-09-29): Attrs should map strings to values, not strings - const char * value = context->to_xml_node()->get_attr(name->c_str()); - return wrap_value(value)->acquire(); - } - - case VAR_NAME: - case FUNC_NAME: - if (scope) { - if (resolve) { - value_t temp; - if (scope->resolve(*name, temp)) - return wrap_value(temp)->acquire(); - } - if (op_t * def = scope->lookup(*name)) - return def->compile(context, scope, resolve); - } - return acquire(); - - case ARG_INDEX: - if (scope && scope->kind == scope_t::ARGUMENT) { - assert(scope->args.type == value_t::SEQUENCE); - if (arg_index < scope->args.to_sequence()->size()) - return wrap_value((*scope->args.to_sequence())[arg_index])->acquire(); - else - throw_(compile_exception, "Reference to non-existing argument"); - } else { - return acquire(); - } - - case FUNCTOR: - if (resolve) { - value_t temp; - (*functor)(temp, scope); - return wrap_value(temp)->acquire(); - } else { - return acquire(); - } - break; - -#if 0 - case MASK: - return acquire(); -#endif - - case O_NOT: { - assert(left); - xpath_t expr(left->compile(context, scope, resolve)); - if (! expr->constant()) { - if (left == expr) - return acquire(); - else - return copy(expr)->acquire(); - } - - if (left == expr) { - if (expr->valuep->strip_annotations()) - return wrap_value(false)->acquire(); - else - return wrap_value(true)->acquire(); - } else { - if (expr->valuep->strip_annotations()) - *expr->valuep = false; - else - *expr->valuep = true; - - return expr->acquire(); - } - } - - case O_NEG: { - assert(left); - xpath_t expr(left->compile(context, scope, resolve)); - if (! expr->constant()) { - if (left == expr) - return acquire(); - else - return copy(expr)->acquire(); - } - - if (left == expr) { - return wrap_value(expr->valuep->negate())->acquire(); - } else { - expr->valuep->in_place_negate(); - return expr->acquire(); - } - } - - case O_UNION: { - assert(left); - assert(right); - xpath_t lexpr(left->compile(context, scope, resolve)); - xpath_t rexpr(right->compile(context, scope, resolve)); - if (! lexpr->constant() || ! rexpr->constant()) { - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - std::auto_ptr result_seq(new value_t::sequence_t); - - append_value(*lexpr->valuep, *result_seq); - append_value(*rexpr->valuep, *result_seq); - - if (result_seq->size() == 1) - return wrap_value(result_seq->front())->acquire(); - else - return wrap_sequence(result_seq.release())->acquire(); - break; - } - - case O_ADD: - case O_SUB: - case O_MUL: - case O_DIV: { - assert(left); - assert(right); - xpath_t lexpr(left->compile(context, scope, resolve)); - xpath_t rexpr(right->compile(context, scope, resolve)); - if (! lexpr->constant() || ! rexpr->constant()) { - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - if (left == lexpr) { - value_t temp(*lexpr->valuep); - switch (kind) { - case O_ADD: temp += *rexpr->valuep; break; - case O_SUB: temp -= *rexpr->valuep; break; - case O_MUL: temp *= *rexpr->valuep; break; - case O_DIV: temp /= *rexpr->valuep; break; - default: assert(0); break; - } - return wrap_value(temp)->acquire(); - } else { - switch (kind) { - case O_ADD: *lexpr->valuep += *rexpr->valuep; break; - case O_SUB: *lexpr->valuep -= *rexpr->valuep; break; - case O_MUL: *lexpr->valuep *= *rexpr->valuep; break; - case O_DIV: *lexpr->valuep /= *rexpr->valuep; break; - default: assert(0); break; - } - return lexpr->acquire(); - } - } - - case O_NEQ: - case O_EQ: - case O_LT: - case O_LTE: - case O_GT: - case O_GTE: { - assert(left); - assert(right); - xpath_t lexpr(left->compile(context, scope, resolve)); - xpath_t rexpr(right->compile(context, scope, resolve)); - if (! lexpr->constant() || ! rexpr->constant()) { - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - if (left == lexpr) { - switch (kind) { - case O_NEQ: - return wrap_value(*lexpr->valuep != *rexpr->valuep)->acquire(); - break; - case O_EQ: - return wrap_value(*lexpr->valuep == *rexpr->valuep)->acquire(); - break; - case O_LT: - return wrap_value(*lexpr->valuep < *rexpr->valuep)->acquire(); - break; - case O_LTE: - return wrap_value(*lexpr->valuep <= *rexpr->valuep)->acquire(); - break; - case O_GT: - return wrap_value(*lexpr->valuep > *rexpr->valuep)->acquire(); - break; - case O_GTE: - return wrap_value(*lexpr->valuep >= *rexpr->valuep)->acquire(); - break; - default: assert(0); break; - } - } else { - switch (kind) { - case O_NEQ: *lexpr->valuep = *lexpr->valuep != *rexpr->valuep; break; - case O_EQ: *lexpr->valuep = *lexpr->valuep == *rexpr->valuep; break; - case O_LT: *lexpr->valuep = *lexpr->valuep < *rexpr->valuep; break; - case O_LTE: *lexpr->valuep = *lexpr->valuep <= *rexpr->valuep; break; - case O_GT: *lexpr->valuep = *lexpr->valuep > *rexpr->valuep; break; - case O_GTE: *lexpr->valuep = *lexpr->valuep >= *rexpr->valuep; break; - default: assert(0); break; - } - return lexpr->acquire(); - } - } - - case O_AND: { - assert(left); - assert(right); - xpath_t lexpr(left->compile(context, scope, resolve)); - if (lexpr->constant() && ! lexpr->valuep->strip_annotations()) { - *lexpr->valuep = false; - return lexpr->acquire(); - } - - xpath_t rexpr(right->compile(context, scope, resolve)); - if (! lexpr->constant() || ! rexpr->constant()) { - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - if (! rexpr->valuep->strip_annotations()) { - if (left == lexpr) { - return wrap_value(false)->acquire(); - } else { - *lexpr->valuep = false; - return lexpr->acquire(); - } - } else { - return rexpr->acquire(); - } - } - - case O_OR: { - assert(left); - assert(right); - xpath_t lexpr(left->compile(context, scope, resolve)); - if (lexpr->constant() && lexpr->valuep->strip_annotations()) - return lexpr->acquire(); - - xpath_t rexpr(right->compile(context, scope, resolve)); - if (! lexpr->constant() || ! rexpr->constant()) { - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - if (rexpr->valuep->strip_annotations()) { - return rexpr->acquire(); - } else { - if (left == lexpr) { - return wrap_value(false)->acquire(); - } else { - *lexpr->valuep = false; - return lexpr->acquire(); - } - } - } - - case O_QUES: { - assert(left); - assert(right); - assert(right->kind == O_COLON); - xpath_t lexpr(left->compile(context, scope, resolve)); - if (! lexpr->constant()) { - xpath_t rexpr(right->compile(context, scope, resolve)); - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - if (lexpr->valuep->strip_annotations()) - return right->left->compile(context, scope, resolve); - else - return right->right->compile(context, scope, resolve); - } - - case O_COLON: { - xpath_t lexpr(left->compile(context, scope, resolve)); - xpath_t rexpr(right->compile(context, scope, resolve)); - if (left == lexpr && right == rexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - case O_COMMA: { - assert(left); - assert(right); - // jww (2006-09-29): This should act just like union - xpath_t lexpr(left->compile(context, scope, resolve)); // for side-effects - return right->compile(context, scope, resolve); - } - -#if 0 - case O_MATCH: - case O_NMATCH: { - assert(left); - assert(right); - xpath_t rexpr(right->compile(context, scope, resolve)); - xpath_t lexpr(left->compile(context, scope, resolve)); - if (! lexpr->constant() || rexpr->kind != MASK) { - if (left == lexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - if (lexpr->valuep->type != value_t::STRING) - throw_(compile_exception, "Left operand of mask operator is not a string"); - - assert(rexpr->mask); - - bool result = rexpr->mask->match(lexpr->valuep->to_string()); - if (kind == O_NMATCH) - result = ! result; - - if (left == lexpr) { - return wrap_value(result)->acquire(); - } else { - *lexpr->valuep = result; - return lexpr->acquire(); - } - } -#endif - - case O_DEFINE: - assert(left); - assert(right); - if (left->kind == VAR_NAME || left->kind == FUNC_NAME) { - xpath_t rexpr(right->compile(context, scope, resolve)); - if (scope) - scope->define(*left->name, rexpr); - return rexpr->acquire(); - } else { - assert(left->kind == O_EVAL); - assert(left->left->kind == FUNC_NAME); - - std::auto_ptr arg_scope(new scope_t(scope)); - - int index = 0; - op_t * args = left->right; - while (args) { - op_t * arg = args; - if (args->kind == O_COMMA) { - arg = args->left; - args = args->right; - } else { - args = NULL; - } - - // Define the parameter so that on lookup the parser will find - // an ARG_INDEX value. - std::auto_ptr ref(new op_t(ARG_INDEX)); - ref->arg_index = index++; - - assert(arg->kind == NODE_NAME); - arg_scope->define(*arg->name, ref.release()); - } - - // jww (2006-09-16): If I compile the definition of a function, - // I eliminate the possibility of future lookups - //xpath_t rexpr(right->compile(arg_scope.get(), resolve)); - - if (scope) - scope->define(*left->left->name, right); - - return right->acquire(); - } - - case O_EVAL: { - assert(left); - - std::auto_ptr call_args(new scope_t(scope)); - call_args->kind = scope_t::ARGUMENT; - - std::auto_ptr call_seq; - - op_t * args = right; - while (args) { - op_t * arg = args; - if (args->kind == O_COMMA) { - arg = args->left; - args = args->right; - } else { - args = NULL; - } - - if (! call_seq.get()) - call_seq.reset(new value_t::sequence_t); - - // jww (2006-09-15): Need to return a reference to these, if - // there are undetermined arguments! - call_seq->push_back(arg->compile(context, scope, resolve)->value()); - } - - if (call_seq.get()) - call_args->args = call_seq.release(); - - if (left->kind == FUNC_NAME) { - if (resolve) { - value_t temp; - if (scope && scope->resolve(*left->name, temp, call_args.get())) - return wrap_value(temp)->acquire(); - } - - // Don't compile to the left, otherwise the function name may - // get resolved before we have a chance to call it - xpath_t func(left->compile(context, scope, false)); - if (func->kind == FUNCTOR) { - value_t temp; - (*func->functor)(temp, call_args.get()); - return wrap_value(temp)->acquire(); - } - else if (! resolve) { - return func->compile(context, call_args.get(), resolve); - } - else { - throw_(calc_exception, "Unknown function name '" << *left->name << "'"); - } - } - else if (left->kind == FUNCTOR) { - value_t temp; - (*left->functor)(temp, call_args.get()); - return wrap_value(temp)->acquire(); - } - else { - assert(0); - } - break; - } - - case O_FIND: - case O_RFIND: - case O_PRED: { - assert(left); - assert(right); - xpath_t lexpr(left->compile(context, scope, resolve)); - xpath_t rexpr(resolve ? right->acquire() : - right->compile(context, scope, false)); - - if (! lexpr->constant() || ! resolve) { - if (left == lexpr) - return acquire(); - else - return copy(lexpr, rexpr)->acquire(); - } - - std::auto_ptr result_seq(new value_t::sequence_t); - - // jww (2006-09-24): What about when nothing is found? - switch (lexpr->valuep->type) { - case value_t::XML_NODE: { - function_scope_t xpath_fscope(NULL, lexpr->valuep, 0, scope); - if (kind == O_PRED) { - if (rexpr->test_value(lexpr->valuep, &xpath_fscope)) - result_seq->push_back(*lexpr->valuep); - } else { - rexpr->find_values(lexpr->valuep, &xpath_fscope, *result_seq.get(), - kind == O_RFIND); - } - break; - } - - case value_t::SEQUENCE: { - value_t::sequence_t * seq = lexpr->valuep->to_sequence(); - - int index = 0; - for (value_t::sequence_t::iterator i = seq->begin(); - i != seq->end(); - i++, index++) { - assert((*i).type != value_t::SEQUENCE); - if ((*i).type != value_t::XML_NODE) - throw_(compile_exception, "Attempting to apply path selection " - "to non-node(s)"); - - function_scope_t xpath_fscope(seq, &(*i), index, scope); - if (kind == O_PRED) { - if (rexpr->test_value(&(*i), &xpath_fscope, index)) - result_seq->push_back(*i); - } else { - rexpr->find_values(&(*i), &xpath_fscope, *result_seq.get(), - kind == O_RFIND); - } - } - break; - } - - default: - throw_(compile_exception, "Attempting to apply path selection " - "to non-node(s)"); - } - - if (result_seq->size() == 1) - return wrap_value(result_seq->front())->acquire(); - else - return wrap_sequence(result_seq.release())->acquire(); - } - -#if 0 - case O_PERC: { - assert(left); - xpath_t expr(left->compile(context, scope, resolve)); - if (! expr->constant()) { - if (left == expr) - return acquire(); - else - return copy(expr)->acquire(); - } - - static value_t perc("100.0%"); - *expr->valuep = perc * *expr->valuep; - return expr->acquire(); - } -#endif - - case LAST: - default: - assert(0); - break; - } -#if 0 - } - catch (error * err) { -#if 0 - // jww (2006-09-09): I need a reference to the parent xpath_t - if (err->context.empty() || - ! dynamic_cast(err->context.back())) - err->context.push_back(new context(this)); -#endif - throw err; - } -#endif - - assert(0); - return NULL; -} - -void xpath_t::calc(value_t& result, node_t * node, scope_t * scope) const -{ -#if 0 - try { -#endif - if (node) { - value_t context_node(node); - xpath_t final(ptr->compile(&context_node, scope, true)); - // jww (2006-09-09): Give a better error here if this is not - // actually a value - final->get_value(result); - } else { - std::auto_ptr fake_node(new terminal_node_t(NULL)); - value_t context_node(fake_node.get()); - xpath_t final(ptr->compile(&context_node, scope, true)); - final->get_value(result); - } -#if 0 - } - catch (error * err) { - if (err->context.empty() || - ! dynamic_cast(err->context.back())) - err->context.push_back - (new context(*this, ptr, "While calculating value expression:")); -#if 0 - error_context * last = err->context.back(); - if (context * ctxt = dynamic_cast(last)) { - ctxt->xpath = *this; - ctxt->desc = "While calculating value expression:"; - } -#endif - throw err; - } -#endif -} - -#if 0 -xpath_t::context::context(const xpath_t& _xpath, - const op_t * _err_node, - const string& desc) throw() - : error_context(desc), xpath(_xpath), err_node(_err_node) -{ - _err_node->acquire(); -} - -xpath_t::context::~context() throw() -{ - if (err_node) err_node->release(); -} - -void xpath_t::context::describe(std::ostream& out) const throw() -{ - if (! xpath) { - out << "xpath_t::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 = false; - if (xpath) - xpath.write(out, true, err_node, &begin, &end); - out << std::endl; - if (found) { - out << " "; - for (unsigned int i = 0; i < end - start; i++) { - if (i >= begin - start) - out << "^"; - else - out << " "; - } - out << std::endl; - } -} -#endif - -bool xpath_t::op_t::write(std::ostream& out, - const bool relaxed, - const op_t * op_to_find, - unsigned long * start_pos, - unsigned long * end_pos) const -{ - int arg_index = 0; - bool found = false; - - if (start_pos && this == op_to_find) { - *start_pos = (long)out.tellp() - 1; - found = true; - } - - string symbol; - - switch (kind) { - case VALUE: - switch (valuep->type) { - case value_t::BOOLEAN: - if (*(valuep)) - out << "1"; - else - out << "0"; - break; - case value_t::INTEGER: - case value_t::AMOUNT: - if (! relaxed) - out << '{'; - out << *(valuep); - if (! relaxed) - out << '}'; - break; - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - assert(0); - break; - case value_t::DATETIME: - out << '[' << *valuep << ']'; - break; - case value_t::STRING: - out << '"' << *valuep << '"'; - break; - - case value_t::XML_NODE: - out << '<' << valuep << '>'; - break; - case value_t::POINTER: - out << '&' << valuep; - break; - case value_t::SEQUENCE: - out << '~' << valuep << '~'; - break; - } - break; - - case NODE_ID: -#ifdef THREADSAFE - out << '%' << name_id; -#else - out << node_t::document->lookup_name(name_id); -#endif - break; - - case NODE_NAME: - case FUNC_NAME: - out << *name; - break; - - case ATTR_NAME: - out << '@' << *name; - break; - - case VAR_NAME: - out << '$' << *name; - break; - - case FUNCTOR: - out << functor->name(); - break; - -#if 0 - case MASK: - out << '/' << mask->pattern << '/'; - break; -#endif - - case ARG_INDEX: - out << '@' << arg_index; - break; - - case O_NOT: - out << "!"; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - case O_NEG: - out << "-"; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - - case O_UNION: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " | "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - - case O_ADD: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " + "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_SUB: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " - "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_MUL: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " * "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_DIV: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " / "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - - case O_NEQ: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " != "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_EQ: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " == "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_LT: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " < "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_LTE: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " <= "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_GT: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " > "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_GTE: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " >= "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - - case O_AND: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " & "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_OR: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " | "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - - case O_QUES: - out << "("; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " ? "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - case O_COLON: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " : "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - - case O_COMMA: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ", "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - -#if 0 - case O_MATCH: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " =~ "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - case O_NMATCH: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << " !~ "; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; -#endif - - case O_DEFINE: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << '='; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - case O_EVAL: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << "("; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << ")"; - break; - - case O_FIND: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << "/"; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - case O_RFIND: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << "//"; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; - case O_PRED: - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << "["; - if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - out << "]"; - break; - -#if 0 - case O_PERC: - out << "%"; - if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) - found = true; - break; -#endif - - case LAST: - default: - assert(0); - break; - } - - if (! symbol.empty()) { - if (commodity_t::find(symbol)) - out << '@'; - out << symbol; - } - - if (end_pos && this == op_to_find) - *end_pos = (long)out.tellp() - 1; - - return found; -} - -void xpath_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 - " << *valuep; - break; - - case NODE_NAME: - out << "NODE_NAME - " << *name; - break; - - case NODE_ID: -#ifdef THREADSAFE - out << "NODE_ID - " << name_id; -#else - out << "NODE_ID - " << node_t::document->lookup_name(name_id); -#endif - break; - - case ATTR_NAME: - out << "ATTR_NAME - " << *name; - break; - - case FUNC_NAME: - out << "FUNC_NAME - " << *name; - break; - - case VAR_NAME: - out << "VAR_NAME - " << *name; - break; - - case ARG_INDEX: - out << "ARG_INDEX - " << arg_index; - break; - - case FUNCTOR: - out << "FUNCTOR - " << functor->name(); - break; -#if 0 - case MASK: - out << "MASK - " << mask->pattern; - break; -#endif - - case O_NOT: out << "O_NOT"; break; - case O_NEG: out << "O_NEG"; break; - - case O_UNION: out << "O_UNION"; 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_QUES: out << "O_QUES"; break; - case O_COLON: out << "O_COLON"; break; - - case O_COMMA: out << "O_COMMA"; break; - -#if 0 - case O_MATCH: out << "O_MATCH"; break; - case O_NMATCH: out << "O_NMATCH"; break; -#endif - - case O_DEFINE: out << "O_DEFINE"; break; - case O_EVAL: out << "O_EVAL"; break; - - case O_FIND: out << "O_FIND"; break; - case O_RFIND: out << "O_RFIND"; break; - case O_PRED: out << "O_PRED"; break; - -#if 0 - case O_PERC: out << "O_PERC"; break; -#endif - - case LAST: - default: - assert(0); - 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); - } - } else { - assert(! left); - } -} - -} // namespace xml -} // namespace ledger diff --git a/xpath.h b/xpath.h deleted file mode 100644 index 13966ffc..00000000 --- a/xpath.h +++ /dev/null @@ -1,783 +0,0 @@ -#ifndef _XPATH_H -#define _XPATH_H - -#include "xml.h" - -namespace ledger { -namespace xml { - -class xpath_t -{ -public: - struct op_t; - - static void initialize(); - static void shutdown(); - - DECLARE_EXCEPTION(parse_exception); - DECLARE_EXCEPTION(compile_exception); - DECLARE_EXCEPTION(calc_exception); - -#if 0 - class context : public error_context { - public: - const xpath_t& xpath; - const op_t * err_node; - - context(const xpath_t& _xpath, - const op_t * _err_node, - const string& desc = "") throw(); - virtual ~context() throw(); - - virtual void describe(std::ostream& out) const throw(); - }; -#endif - -public: - class scope_t; - - class functor_t { - protected: - string fname; - public: - bool wants_args; - - functor_t(const string& _fname, bool _wants_args = false) - : fname(_fname), wants_args(_wants_args) {} - virtual ~functor_t() {} - - virtual void operator()(value_t& result, scope_t * locals) = 0; - virtual string name() const { return fname; } - }; - - template - class member_functor_t : public functor_t { - public: - T * ptr; - U T::*dptr; - - member_functor_t(const string& _name, T * _ptr, U T::*_dptr) - : functor_t(_name, false), ptr(_ptr), dptr(_dptr) {} - - virtual void operator()(value_t& result, scope_t * locals) { - assert(ptr); - assert(dptr); - result = ptr->*dptr; - } - }; - - template - class member_functor_t : public functor_t { - public: - T * ptr; - string T::*dptr; - - member_functor_t(const string& _name, T * _ptr, string T::*_dptr) - : functor_t(_name, false), ptr(_ptr), dptr(_dptr) {} - - virtual void operator()(value_t& result, scope_t * locals) { - assert(ptr); - assert(dptr); - result.set_string(ptr->*dptr); - } - }; - - template - class memfun_functor_t : public functor_t { - public: - T * ptr; - void (T::*mptr)(value_t& result); - - memfun_functor_t(const string& _name, T * _ptr, - void (T::*_mptr)(value_t& result)) - : functor_t(_name, false), ptr(_ptr), mptr(_mptr) {} - - virtual void operator()(value_t& result, - scope_t * locals = NULL) { - assert(ptr); - assert(mptr); - assert(locals || locals == NULL); - (ptr->*mptr)(result); - } - }; - - template - class memfun_args_functor_t : public functor_t { - public: - T * ptr; - void (T::*mptr)(value_t& result, scope_t * locals); - - memfun_args_functor_t(const string& _name, T * _ptr, - void (T::*_mptr)(value_t& result, scope_t * locals)) - : functor_t(_name, true), ptr(_ptr), mptr(_mptr) {} - - virtual void operator()(value_t& result, scope_t * locals) { - assert(ptr); - assert(mptr); - (ptr->*mptr)(result, locals); - } - }; - - static op_t * wrap_value(const value_t& val); - static op_t * wrap_sequence(value_t::sequence_t * val); - static op_t * wrap_functor(functor_t * fobj); -#if 0 - static op_t * wrap_mask(const string& pattern); -#endif - - template - static op_t * - make_functor(const string& name, T * ptr, U T::*mptr) { - return wrap_functor(new member_functor_t(name, ptr, mptr)); - } - - template - static op_t * - make_functor(const string& fname, T * ptr, - void (T::*mptr)(value_t& result)) { - return wrap_functor(new memfun_functor_t(fname, ptr, mptr)); - } - - template - static op_t * - make_functor(const string& fname, T * ptr, - void (T::*mptr)(value_t& result, scope_t * locals)) { - return wrap_functor(new memfun_args_functor_t(fname, ptr, mptr)); - } - -#define MAKE_FUNCTOR(cls, name) \ - xml::xpath_t::make_functor(#name, this, &cls::name) - -public: - class scope_t - { - typedef std::map symbol_map; - typedef std::pair symbol_pair; - - symbol_map symbols; - - scope_t(const scope_t&); - scope_t& operator=(const scope_t&); - - public: - scope_t * parent; - value_t args; - - enum kind_t { NORMAL, STATIC, ARGUMENT } kind; - - scope_t(scope_t * _parent = NULL, kind_t _kind = NORMAL) - : parent(_parent), kind(_kind) { - TRACE_CTOR(xpath_t::scope_t, "scope *, kind_t"); - } - - virtual ~scope_t() { - TRACE_DTOR(xpath_t::scope_t); - for (symbol_map::iterator i = symbols.begin(); - i != symbols.end(); - i++) - (*i).second->release(); - } - - public: - virtual void define(const string& name, op_t * def); - virtual bool resolve(const string& name, value_t& result, - scope_t * locals = NULL) { - if (parent) - return parent->resolve(name, result, locals); - return false; - } - virtual op_t * lookup(const string& name); - - void define(const string& name, functor_t * def); - - friend struct op_t; - }; - - class function_scope_t : public scope_t - { - value_t::sequence_t * sequence; - value_t * value; - int index; - - public: - function_scope_t(value_t::sequence_t * _sequence, value_t * _value, - int _index, scope_t * _parent = NULL) - : scope_t(_parent, STATIC), - sequence(_sequence), value(_value), index(_index) {} - - virtual bool resolve(const string& name, value_t& result, - scope_t * locals = NULL); - }; - -#define XPATH_PARSE_NORMAL 0x00 -#define XPATH_PARSE_PARTIAL 0x01 -#define XPATH_PARSE_RELAXED 0x02 -#define XPATH_PARSE_NO_MIGRATE 0x04 -#define XPATH_PARSE_NO_REDUCE 0x08 -#if 0 -#define XPATH_PARSE_REGEXP 0x10 -#endif -#define XPATH_PARSE_ALLOW_DATE 0x20 - -private: - struct token_t - { - enum kind_t { - IDENT, // [A-Za-z_][-A-Za-z0-9_:]* - VALUE, // any kind of literal value -#if 0 - REGEXP, // /regexp/ jww (2006-09-24): deprecate - // in favor of a "match" function -#endif - AT_SYM, // @ - DOLLAR, // $ - DOT, // . - DOTDOT, // .. - LPAREN, // ( - RPAREN, // ) - LBRACKET, // ( - RBRACKET, // ) - EXCLAM, // ! - NEQUAL, // != - MINUS, // - - PLUS, // + - STAR, // * - POWER, // ** - SLASH, // / - EQUAL, // = - ASSIGN, // := - LESS, // < - LESSEQ, // <= - GREATER, // > - GREATEREQ, // >= - AMPER, // & - PIPE, // | - QUESTION, // ? - COLON, // : - COMMA, // , -#if 0 - MATCH, // =~ - NMATCH, // !~ - PERCENT, // % -#endif - KW_AND, - KW_OR, - KW_DIV, - KW_MOD, - KW_UNION, - TOK_EOF, - UNKNOWN - } kind; - - char symbol[3]; - value_t value; - unsigned int length; - - token_t() : kind(UNKNOWN), length(0) { - TRACE_CTOR(xpath_t::token_t, ""); - } - - token_t(const token_t& other) { - assert(0); - TRACE_CTOR(xpath_t::token_t, "copy"); - *this = other; - } - - ~token_t() { - TRACE_DTOR(xpath_t::token_t); - } - - token_t& operator=(const token_t& other) { - if (&other == this) - return *this; - assert(0); - return *this; - } - - void clear() { - kind = UNKNOWN; - length = 0; - value = 0L; - - symbol[0] = '\0'; - symbol[1] = '\0'; - symbol[2] = '\0'; - } - - void parse_ident(std::istream& in); - void next(std::istream& in, unsigned short flags); - void rewind(std::istream& in); - void unexpected(); - - static void unexpected(char c, char wanted = '\0'); - }; - -public: - struct op_t - { - enum kind_t { - VOID, - VALUE, - - NODE_NAME, - NODE_ID, - FUNC_NAME, - ATTR_NAME, - VAR_NAME, - - ARG_INDEX, - - CONSTANTS, // constants end here - - FUNCTOR, -#if 0 - MASK, -#endif - - TERMINALS, // terminals end here - - O_NOT, - O_NEG, - - O_UNION, - - O_ADD, - O_SUB, - O_MUL, - O_DIV, - - O_NEQ, - O_EQ, - O_LT, - O_LTE, - O_GT, - O_GTE, - - O_AND, - O_OR, - - O_QUES, - O_COLON, - - O_COMMA, - -#if 0 - O_MATCH, - O_NMATCH, -#endif - - O_DEFINE, - O_EVAL, - O_ARG, - -#if 0 - O_PERC, -#endif - - O_FIND, - O_RFIND, - O_PRED, - - LAST // operators end here - }; - - kind_t kind; - mutable short refc; - op_t * left; - - union { - value_t * valuep; // used by constant VALUE - string * name; // used by constant SYMBOL - unsigned int arg_index; // used by ARG_INDEX and O_ARG - functor_t * functor; // used by terminal FUNCTOR - unsigned int name_id; // used by NODE_NAME and ATTR_NAME -#if 0 - mask_t * mask; // used by terminal MASK -#endif - op_t * right; // used by all operators - }; - - op_t(const kind_t _kind) - : kind(_kind), refc(0), left(NULL), right(NULL) { - TRACE_CTOR(xpath_t::op_t, "const kind_t"); - } - op_t(const op_t&); - ~op_t(); - - op_t& operator=(const op_t&); - - bool constant() const { - return kind == VALUE; - } - void get_value(value_t& result) const; - value_t value() const { - value_t temp; - get_value(temp); - return temp; - } - - functor_t * functor_obj() const { - if (kind == FUNCTOR) - return functor; - else - return NULL; - } - - void release() const { - DEBUG_("ledger.xpath.memory", - "Releasing " << this << ", refc now " << refc - 1); - assert(refc > 0); - if (--refc == 0) - delete this; - } - op_t * acquire() { - DEBUG_("ledger.xpath.memory", - "Acquiring " << this << ", refc now " << refc + 1); - assert(refc >= 0); - refc++; - return this; - } - const op_t * acquire() const { - DEBUG_("ledger.xpath.memory", - "Acquiring " << this << ", refc now " << refc + 1); - assert(refc >= 0); - refc++; - return this; - } - - void set_left(op_t * expr) { - assert(kind > TERMINALS); - if (left) - left->release(); - left = expr ? expr->acquire() : NULL; - } - - void set_right(op_t * expr) { - assert(kind > TERMINALS); - if (right) - right->release(); - right = expr ? expr->acquire() : NULL; - } - - static op_t * new_node(kind_t kind, op_t * left = NULL, - op_t * right = NULL); - - op_t * copy(op_t * left = NULL, - op_t * right = NULL) const; - op_t * compile(value_t * context, scope_t * scope, - bool resolve = false); - - void find_values(value_t * context, scope_t * scope, - value_t::sequence_t& result_seq, bool recursive); - bool test_value(value_t * context, scope_t * scope, int index = 0); - - void append_value(value_t& value, value_t::sequence_t& result_seq); - - static op_t * defer_sequence(value_t::sequence_t& result_seq); - - bool write(std::ostream& out, - const bool relaxed = true, - const op_t * op_to_find = NULL, - unsigned long * start_pos = NULL, - unsigned long * end_pos = NULL) const; - - void dump(std::ostream& out, const int depth) const; - }; - -public: - op_t * ptr; - - xpath_t& operator=(op_t * _expr) { - expr = ""; - reset(_expr); - return *this; - } - - op_t& operator*() throw() { - return *ptr; - } - const op_t& operator*() const throw() { - return *ptr; - } - op_t * operator->() throw() { - return ptr; - } - const op_t * operator->() const throw() { - return ptr; - } - - op_t * get() throw() { return ptr; } - const op_t * get() const throw() { return ptr; } - - op_t * release() throw() { - op_t * tmp = ptr; - ptr = 0; - return tmp; - } - - void reset(op_t * p = 0) throw() { - if (p != ptr) { - if (ptr) - ptr->release(); - ptr = p; - } - } - -#ifdef THREADSAFE - mutable token_t lookahead; -#else - static token_t * lookahead; -#endif - mutable bool use_lookahead; - - token_t& next_token(std::istream& in, unsigned short tflags) const { - if (use_lookahead) - use_lookahead = false; - else -#ifdef THREADSAFE - lookahead.next(in, tflags); -#else - lookahead->next(in, tflags); -#endif -#ifdef THREADSAFE - return lookahead; -#else - return *lookahead; -#endif - } - void push_token(const token_t& tok) const { -#ifdef THREADSAFE - assert(&tok == &lookahead); -#else - assert(&tok == lookahead); -#endif - use_lookahead = true; - } - void push_token() const { - use_lookahead = true; - } - - op_t * parse_value_term(std::istream& in, unsigned short flags) const; - op_t * parse_predicate_expr(std::istream& in, unsigned short flags) const; - op_t * parse_path_expr(std::istream& in, unsigned short flags) const; - op_t * parse_unary_expr(std::istream& in, unsigned short flags) const; - op_t * parse_union_expr(std::istream& in, unsigned short flags) const; - op_t * parse_mul_expr(std::istream& in, unsigned short flags) const; - op_t * parse_add_expr(std::istream& in, unsigned short flags) const; - op_t * parse_logic_expr(std::istream& in, unsigned short flags) const; - op_t * parse_and_expr(std::istream& in, unsigned short flags) const; - op_t * parse_or_expr(std::istream& in, unsigned short flags) const; - op_t * parse_querycolon_expr(std::istream& in, unsigned short flags) const; - op_t * parse_value_expr(std::istream& in, unsigned short flags) const; - - op_t * parse_expr(std::istream& in, - unsigned short flags = XPATH_PARSE_RELAXED) const; - - op_t * parse_expr(const string& str, - unsigned short tflags = XPATH_PARSE_RELAXED) const - { - std::istringstream stream(str); -#if 0 - try { -#endif - return parse_expr(stream, tflags); -#if 0 - } - catch (error * err) { - err->context.push_back - (new line_context(str, (long)stream.tellg() - 1, - "While parsing value expression:")); - throw err; - } -#endif - } - - op_t * parse_expr(const char * p, - unsigned short tflags = XPATH_PARSE_RELAXED) const { - return parse_expr(string(p), tflags); - } - - bool write(std::ostream& out, - const bool relaxed, - const op_t * op_to_find, - unsigned long * start_pos, - unsigned long * end_pos) const { - if (ptr) - ptr->write(out, relaxed, op_to_find, start_pos, end_pos); - return true; - } - -public: - string expr; - unsigned short flags; // flags used to parse `expr' - - xpath_t() : ptr(NULL), use_lookahead(false), flags(0) { - TRACE_CTOR(xpath_t, ""); - } - xpath_t(op_t * _ptr) : ptr(_ptr), use_lookahead(false) { - TRACE_CTOR(xpath_t, "op_t *"); - } - - xpath_t(const string& _expr, - unsigned short _flags = XPATH_PARSE_RELAXED) - : ptr(NULL), use_lookahead(false), flags(0) { - TRACE_CTOR(xpath_t, "const string&, unsigned short"); - if (! _expr.empty()) - parse(_expr, _flags); - } - xpath_t(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) - : ptr(NULL), use_lookahead(false), flags(0) { - TRACE_CTOR(xpath_t, "std::istream&, unsigned short"); - parse(in, _flags); - } - xpath_t(const xpath_t& other) - : ptr(other.ptr ? other.ptr->acquire() : NULL), - use_lookahead(false), expr(other.expr), flags(other.flags) { - TRACE_CTOR(xpath_t, "copy"); - } - virtual ~xpath_t() { - TRACE_DTOR(xpath_t); - reset(NULL); - } - - xpath_t& operator=(const string& _expr) { - parse(_expr); - return *this; - } - xpath_t& operator=(const xpath_t& _expr); - xpath_t& operator=(xpath_t& _xpath) { - ptr = _xpath.ptr->acquire(); - expr = _xpath.expr; - flags = _xpath.flags; - use_lookahead = false; - return *this; - } - - operator op_t *() throw() { - return ptr; - } - - operator bool() const throw() { - return ptr != NULL; - } - operator string() const throw() { - return expr; - } - - void parse(const string& _expr, unsigned short _flags = XPATH_PARSE_RELAXED) { - expr = _expr; - flags = _flags; - op_t * tmp = parse_expr(_expr, _flags); - assert(tmp); - reset(tmp ? tmp->acquire() : NULL); - } - void parse(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) { - expr = ""; - flags = _flags; - op_t * tmp = parse_expr(in, _flags); - assert(tmp); - reset(tmp ? tmp->acquire() : NULL); - } - - void compile(const string& _expr, scope_t * scope = NULL, - unsigned short _flags = XPATH_PARSE_RELAXED) { - parse(_expr, _flags); - // jww (2006-09-24): fix - compile((node_t *)NULL, scope); - } - void compile(std::istream& in, scope_t * scope = NULL, - unsigned short _flags = XPATH_PARSE_RELAXED) { - parse(in, _flags); - // jww (2006-09-24): fix - compile((node_t *)NULL, scope); - } - - void compile(document_t * document, scope_t * scope = NULL) { - if (! document) { - document_t tdoc; - compile(tdoc.top, scope); - } else { - compile(document->top, scope); - } - } - void compile(node_t * top_node, scope_t * scope = NULL) { - if (ptr) { - value_t noderef(top_node); - op_t * compiled = ptr->compile(&noderef, scope); - if (compiled == ptr) - compiled->release(); - else - reset(compiled); - } - } - - virtual void calc(value_t& result, node_t * node, scope_t * scope = NULL) const; - - virtual value_t calc(document_t * document, scope_t * scope = NULL) const { - if (! ptr) - return 0L; - value_t temp; - calc(temp, document ? document->top : NULL, scope); - return temp; - } - virtual value_t calc(node_t * tcontext, scope_t * scope = NULL) const { - if (! ptr) - return 0L; - value_t temp; - calc(temp, tcontext, scope); - return temp; - } - - static void eval(value_t& result, const string& _expr, - document_t * document, scope_t * scope = NULL) { - xpath_t temp(_expr); - temp.calc(result, document->top, scope); - } - static value_t eval(const string& _expr, document_t * document, - scope_t * scope = NULL) { - xpath_t temp(_expr); - return temp.calc(document, scope); - } - - void write(std::ostream& out) const { - write(out, true, NULL, NULL, NULL); - } - void dump(std::ostream& out) const { - if (ptr) - ptr->dump(out, 0); - } - - friend class scope_t; -}; - -inline std::ostream& operator<<(std::ostream& out, const xpath_t::op_t& op) { - op.write(out); - return out; -}; - -} // namespace xml - -template -inline T * get_ptr(xml::xpath_t::scope_t * locals, unsigned int idx) { - assert(locals->args.size() > idx); - T * ptr = static_cast(locals->args[idx].to_pointer()); - assert(ptr); - return ptr; -} - -class xml_command : public xml::xpath_t::functor_t -{ - public: - xml_command() : xml::xpath_t::functor_t("xml") {} - - virtual void operator()(value_t&, xml::xpath_t::scope_t * locals) { - std::ostream * out = get_ptr(locals, 0); - xml::document_t * doc = get_ptr(locals, 1); - - doc->write(*out); - } -}; - -} // namespace ledger - -#endif // _XPATH_H diff --git a/ylwrap b/ylwrap deleted file mode 100755 index 102bd893..00000000 --- a/ylwrap +++ /dev/null @@ -1,223 +0,0 @@ -#! /bin/sh -# ylwrap - wrapper for lex/yacc invocations. - -scriptversion=2005-05-14.22 - -# Copyright (C) 1996, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2005 -# Free Software Foundation, Inc. -# -# Written by Tom Tromey . -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301, USA. - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# This file is maintained in Automake, please report -# bugs to or send patches to -# . - -case "$1" in - '') - echo "$0: No files given. Try \`$0 --help' for more information." 1>&2 - exit 1 - ;; - --basedir) - basedir=$2 - shift 2 - ;; - -h|--h*) - cat <<\EOF -Usage: ylwrap [--help|--version] INPUT [OUTPUT DESIRED]... -- PROGRAM [ARGS]... - -Wrapper for lex/yacc invocations, renaming files as desired. - - INPUT is the input file - OUTPUT is one file PROG generates - DESIRED is the file we actually want instead of OUTPUT - PROGRAM is program to run - ARGS are passed to PROG - -Any number of OUTPUT,DESIRED pairs may be used. - -Report bugs to . -EOF - exit $? - ;; - -v|--v*) - echo "ylwrap $scriptversion" - exit $? - ;; -esac - - -# The input. -input="$1" -shift -case "$input" in - [\\/]* | ?:[\\/]*) - # Absolute path; do nothing. - ;; - *) - # Relative path. Make it absolute. - input="`pwd`/$input" - ;; -esac - -pairlist= -while test "$#" -ne 0; do - if test "$1" = "--"; then - shift - break - fi - pairlist="$pairlist $1" - shift -done - -# The program to run. -prog="$1" -shift -# Make any relative path in $prog absolute. -case "$prog" in - [\\/]* | ?:[\\/]*) ;; - *[\\/]*) prog="`pwd`/$prog" ;; -esac - -# FIXME: add hostname here for parallel makes that run commands on -# other machines. But that might take us over the 14-char limit. -dirname=ylwrap$$ -trap "cd `pwd`; rm -rf $dirname > /dev/null 2>&1" 1 2 3 15 -mkdir $dirname || exit 1 - -cd $dirname - -case $# in - 0) $prog "$input" ;; - *) $prog "$@" "$input" ;; -esac -ret=$? - -if test $ret -eq 0; then - set X $pairlist - shift - first=yes - # Since DOS filename conventions don't allow two dots, - # the DOS version of Bison writes out y_tab.c instead of y.tab.c - # and y_tab.h instead of y.tab.h. Test to see if this is the case. - y_tab_nodot="no" - if test -f y_tab.c || test -f y_tab.h; then - y_tab_nodot="yes" - fi - - # The directory holding the input. - input_dir=`echo "$input" | sed -e 's,\([\\/]\)[^\\/]*$,\1,'` - # Quote $INPUT_DIR so we can use it in a regexp. - # FIXME: really we should care about more than `.' and `\'. - input_rx=`echo "$input_dir" | sed 's,\\\\,\\\\\\\\,g;s,\\.,\\\\.,g'` - - while test "$#" -ne 0; do - from="$1" - # Handle y_tab.c and y_tab.h output by DOS - if test $y_tab_nodot = "yes"; then - if test $from = "y.tab.c"; then - from="y_tab.c" - else - if test $from = "y.tab.h"; then - from="y_tab.h" - fi - fi - fi - if test -f "$from"; then - # If $2 is an absolute path name, then just use that, - # otherwise prepend `../'. - case "$2" in - [\\/]* | ?:[\\/]*) target="$2";; - *) target="../$2";; - esac - - # We do not want to overwrite a header file if it hasn't - # changed. This avoid useless recompilations. However the - # parser itself (the first file) should always be updated, - # because it is the destination of the .y.c rule in the - # Makefile. Divert the output of all other files to a temporary - # file so we can compare them to existing versions. - if test $first = no; then - realtarget="$target" - target="tmp-`echo $target | sed s/.*[\\/]//g`" - fi - # Edit out `#line' or `#' directives. - # - # We don't want the resulting debug information to point at - # an absolute srcdir; it is better for it to just mention the - # .y file with no path. - # - # We want to use the real output file name, not yy.lex.c for - # instance. - # - # We want the include guards to be adjusted too. - FROM=`echo "$from" | sed \ - -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'\ - -e 's/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ]/_/g'` - TARGET=`echo "$2" | sed \ - -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'\ - -e 's/[^ABCDEFGHIJKLMNOPQRSTUVWXYZ]/_/g'` - - sed -e "/^#/!b" -e "s,$input_rx,," -e "s,$from,$2," \ - -e "s,$FROM,$TARGET," "$from" >"$target" || ret=$? - - # Check whether header files must be updated. - if test $first = no; then - if test -f "$realtarget" && cmp -s "$realtarget" "$target"; then - echo "$2" is unchanged - rm -f "$target" - else - echo updating "$2" - mv -f "$target" "$realtarget" - fi - fi - else - # A missing file is only an error for the first file. This - # is a blatant hack to let us support using "yacc -d". If -d - # is not specified, we don't want an error when the header - # file is "missing". - if test $first = yes; then - ret=1 - fi - fi - shift - shift - first=no - done -else - ret=$? -fi - -# Remove the directory. -cd .. -rm -rf $dirname - -exit $ret - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-end: "$" -# End: -- cgit v1.2.3