diff options
author | John Wiegley <johnw@newartisans.com> | 2013-04-29 16:36:29 -0500 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2013-04-29 16:36:29 -0500 |
commit | 59550b7f66c31592160749c5177074f63d19fa9d (patch) | |
tree | 0b28be9ab403e67d042f74ae9d1d76d885486b18 | |
parent | 385cbd25b9905b16a4c7723bb4e5a5813e84aab0 (diff) | |
parent | 6bef247759acbdc026624e78d0fd78297bc79501 (diff) | |
download | fork-ledger-59550b7f66c31592160749c5177074f63d19fa9d.tar.gz fork-ledger-59550b7f66c31592160749c5177074f63d19fa9d.tar.bz2 fork-ledger-59550b7f66c31592160749c5177074f63d19fa9d.zip |
Merge branch 'next'
226 files changed, 15275 insertions, 6654 deletions
diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 00000000..4f6af924 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,31 @@ +((nil . ((tab-width . 2) + (sentence-end-double-space . t) + (fill-column . 8) + (bug-reference-url-format + . "http://bugs.ledger-cli.org/show_bug.cgi?id=%s"))) + (c-mode . ((c-file-style . "ledger") + (c-style-alist + ("ledger" + (indent-tabs-mode) + (c-basic-offset . 2) + (c-comment-only-line-offset 0 . 0) + (c-hanging-braces-alist + (substatement-open before after) + (arglist-cont-nonempty)) + (c-offsets-alist + (statement-block-intro . +) + (knr-argdecl-intro . 5) + (substatement-open . 0) + (substatement-label . 0) + (label . 0) + (case-label . 0) + (statement-case-open . 0) + (statement-cont . +) + (arglist-intro . +) + (arglist-close . +) + (inline-open . 0) + (brace-list-open . 0) + (topmost-intro-cont first c-lineup-topmost-intro-cont + c-lineup-gnu-DEFUN-intro-cont)) + (c-special-indent-hook . c-gnu-impose-minimum) + (c-block-comment-prefix . "")))))) @@ -12,7 +12,6 @@ .libs/ ABOUT-NLS BaselineTests -Doxyfile.gen Makefile Makefile.am Makefile.in @@ -34,6 +33,7 @@ configure configure.ac data_tests depcomp +doc/Doxyfile doc/*.aux doc/*.cp doc/*.fn @@ -48,6 +48,7 @@ doc/.dirstamp doc/html/ doc/latex/ doc/ledger.info +doc/ledger3.info doc/refman.pdf doc/report/ elisp-comp @@ -97,3 +98,10 @@ system.hh system.hh.[gp]ch* Ledger*.dmg Ledger*.sh +# Files that generated by running ./demo.sh in contrib/non-profit-audit-reports +contrib/non-profit-audit-reports/tests/chart-of-accounts.txt +contrib/non-profit-audit-reports/tests/general-ledger.csv +contrib/non-profit-audit-reports/tests/general-ledger.ods +contrib/non-profit-audit-reports/tests/general-ledger.txt +contrib/non-profit-audit-reports/tests/MANIFEST +contrib/non-profit-audit-reports/general-ledger.zip diff --git a/.gitmodules b/.gitmodules index 18e192aa..ff480831 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lib/utfcpp"] path = lib/utfcpp - url = git://github.com/jwiegley/utfcpp.git + url = http://github.com/ledger/utfcpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 13962647..f18df69a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 2.8.5) -project(Ledger) +PROJECT(ledger) set(Ledger_VERSION_MAJOR 3) set(Ledger_VERSION_MINOR 0) @@ -18,7 +18,8 @@ option(DISABLE_ASSERTS "Build without any internal consistency checks" OFF) option(BUILD_DEBUG "Build support for runtime debugging" OFF) option(BUILD_LIBRARY "Build and install Ledger as a library" ON) -option(BUILD_DOCS "Build and install documentation" OFF) +option(BUILD_DOCS "Build and install documentation" ON) +option(BUILD_WEB_DOCS "Build version of documentation suitable for viewing online" OFF) option(BUILD_EMACSLISP "Build and install ledger-mode for Emacs" OFF) if(BUILD_DEBUG) @@ -45,6 +46,7 @@ endif() find_package(PythonInterp) # Used for running tests if(USE_PYTHON) + set(Python_ADDITIONAL_VERSIONS 2.7 2.6) find_package(PythonLibs) if(PYTHONLIBS_FOUND) set(BOOST_PYTHON python) @@ -58,11 +60,13 @@ else() set(HAVE_BOOST_PYTHON 0) endif() +# Set BOOST_ROOT to help CMake to find the right Boost version find_package(Boost 1.46.0 REQUIRED date_time filesystem system iostreams regex unit_test_framework ${BOOST_PYTHON}) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) +link_directories(${Boost_LIBRARY_DIRS}) ######################################################################## @@ -272,9 +276,7 @@ elseif(CMAKE_CXX_COMPILER MATCHES "g\\+\\+") endif() add_subdirectory(src) -if(BUILD_DOCS) - add_subdirectory(doc) -endif() +add_subdirectory(doc) if(BUILD_EMACSLISP) add_subdirectory(lisp) endif() @@ -5,9 +5,12 @@ To build this code after doing a Git clone, run: $ ./acprep update -If you try to configure and build on your own, you are almost certainly going -to run into problems. In future, you can run this command again and again, -and it will keep you updated to the very latest version. +If anything goes wrong, see "COMMON CONFIGURE/BUILD PROBLEMS" below. + +If you try to configure and build without running acprep first, you are +almost certainly going to run into problems. In future, you can run +'acprep update' again and again, and it will keep you updated to the +very latest version. I further recommend building both debug and optimized versions of Ledger, in a subdirectory of your source tree named 'build' (which acprep will manage for @@ -17,12 +20,17 @@ you, you simply need to create it): $ ./acprep opt make $ ./acprep debug make -Now install the optimized version, but know that you have 'build/debug/ledger' -available for testing and more useful bug reports. +Now install the optimized version: -=============================================================================== + $ cd build/ledger/opt + $ sudo make install - IF YOU HAVE CONFIGURE OR BUILD PROBLEMS +but know that you have 'build/ledger/debug' available for testing and +for more useful bug reports. + +=============================================================================== + COMMON CONFIGURE/BUILD PROBLEMS +=============================================================================== To build and install Ledger requires several dependencies on various platforms. You can install these dependencies very simply for most of them @@ -41,8 +49,9 @@ With the contents of config.log, and the output from acprep --debug update, it's usually fairly obvious where things have gone astray. =============================================================================== - F.A.Q. +=============================================================================== + - Q: The build fails saying it can't find utf8.h @@ -50,6 +59,29 @@ it's usually fairly obvious where things have gone astray. ---------------------------------------------------------------------- + - Q: './acprep update' gives errors or './acprep dependencies' fails + + A: You're probably missing some dependency libraries. If you tried + './acprep dependencies' already and that didn't solve the problem, + then you may need to install dependencies by hand. On a Debian + GNU/Linux system (or Debian-based system such as Ubuntu), something + like this should work (as root): + + # aptitude update + # for name in \ + cmake libboost-dev libboost-date-time-dev libboost-doc \ + libboost-dbg libboost-filesystem-dev libboost-graph-dev \ + libboost-iostreams-dev libboost-program-options-dev \ + libboost-python-dev libboost-regex-dev \ + libboost-serialization-dev libboost-signals-dev \ + libboost-test-dev libboost-thread-dev libboost-wave-dev \ + libmpfr-dev libmpfr-dbg libmpfr-doc; \ + do \ + aptitude install ${name}; \ + done + + ---------------------------------------------------------------------- + - Q: Configure fails saying it can't find boost_regex A: Look in config.log and search for "boost_regex", then scroll down a bit @@ -68,7 +100,10 @@ it's usually fairly obvious where things have gone astray. - Q: Configure fails saying it can't find MPFR A: You need MPFR version 2.4.0 or higher. This version does not come with - most Debian distributions, so you will need to build it. + most Debian distributions, so you will need to build it. The + relevant packages are 'libmpfr-dev' and 'libmpfr-dbg'. See also + the question above about what to do if './acprep update' gives + errors or './acprep dependencies' fails. ---------------------------------------------------------------------- @@ -82,16 +117,18 @@ it's usually fairly obvious where things have gone astray. - Q: Something else fails, or Ledger crashes on startup - A: This, I am most interested in hearing about. Please e-mail me a copy of - config.log and your build log to <johnw@newartisans.com>. Also, if - Ledger is crashing, try running it under gdb like so: + A: This, I am most interested in hearing about. Please file a bug + at the Ledger Bugzilla, http://bugs.ledger-cli.org/. The more + details you can provide, the better. Also, if Ledger is crashing, + try running it under gdb like so: $ gdb ledger (gdb) run <ARGS TO LEDGER> ... runs till crash ... (gdb) bt - Send me that backtrace output, and the output from "ledger --version". + Put that backtrace output, and the output from "ledger + --version" in the bug report. ---------------------------------------------------------------------- @@ -114,3 +151,42 @@ it's usually fairly obvious where things have gone astray. A: This can happen for the same reason as above. It can also happen if you have ICU support enabled. This is a bug I'm still trying to track down. + + ---------------------------------------------------------------------- + + - Q: My distribution has versions of Boost and/or CMake that are too old for + Ledger. How do I build my own Boost and/or CMake binaries that will + work properly with Ledger? Thereafter, how do I configure Ledger + properly to use those newly built verisons of Boost and/or CMake? + + A: Here's commands that one user used to make this work, for Boost 1.51.0 + on Debian GNU/Linux 6.0.x (aka Debian squeeze). It's likely to work ok + for other versions of Boost as well. YMMV on other distributions and/or + other Debian distribution versions, though. + + # Preparing and building Boost 1.51.0 + + $ cd /somewhere/you/want/to/build/boost + $ wget -N http://iweb.dl.sourceforge.net/project/boost/boost/1.51.0/boost_1_51_0.tar.bz2 + $ tar xvf boost_1_51_0.tar.bz2 + $ cd boost_1_51_0 + $ ./bootstrap.sh + $ ./b2 --build-type=complete --layout=tagged --prefix=/where/you/want/boost/installed + $ ./b2 --build-type=complete --layout=tagged --prefix=/where/you/want/boost/installed install + + # Preparing and building CMake 2.8.8 + + $ cd /somewhere/you/want/to/build/cmake + $ wget -N http://www.cmake.org/files/v2.8/cmake-2.8.8.tar.gz + $ tar xvf cmake-2.8.8.tar.gz + $ cd cmake-2.8.8 + $ ./configure --prefix=/where/you/want/cmake/installed/ + $ make + $ make install + + # Building Ledger using the CMake and/or Boost as installed above + + $ cd /path/to/ledger/sources + $ env PATH=/where/you/want/cmake/installed/bin:$PATH BOOST_ROOT=/where/you/want/boost/installed ./acprep --prefix=/where/you/want/ledger/installed --debug --python config + $ env PATH=/where/you/want/cmake/installed/bin:$PATH BOOST_ROOT=/where/you/want/boost/installed ./acprep --prefix=/where/you/want/ledger/installed --debug --python make + $ env PATH=/where/you/want/cmake/installed/bin:$PATH BOOST_ROOT=/where/you/want/boost/installed ./acprep --prefix=/where/you/want/ledger/installed --debug --python install @@ -10,7 +10,7 @@ few alternatives. I know, you just want to build and play. If you have all the dependencies installed (see below), then simply do this: - git clone git://github.com/jwiegley/ledger.git + git clone git://github.com/ledger/ledger.git cd ledger && ./acprep update # Update to the latest, configure, make Now try your first ledger command: @@ -95,12 +95,12 @@ You can even just install the current Ledger **RELEASE** directly: ### Ubuntu If you're going to build on Ubuntu, `sudo apt-get install ...` the -following packages (current as of Ubuntu Hardy): +following packages (current as of Ubuntu 12.04): sudo apt-get install build-essential cmake zlib1g-dev libbz2-dev - python-dev bjam cvs gettext libgmp3-dev libmpfr-dev libboost1.35-dev - libboost-regex1.35-dev libboost-date-time1.35-dev - libboost-filesystem1.35-dev libboost-python1.35-dev texinfo lcov + python-dev gettext libgmp3-dev libmpfr-dev libboost-dev + libboost-regex-dev libboost-date-time-dev + libboost-filesystem-dev libboost-python-dev texinfo lcov sloccount Or, for Ubuntu Karmic: @@ -122,7 +122,7 @@ command: sudo apt-get install build-essential cmake autopoint texinfo python-dev zlib1g-dev libbz2-dev libgmp3-dev gettext libmpfr-dev libboost-date-time1.49-dev libboost-filesystem1.49-dev - libboost-graph49-dev libboost-iostreams1.49-dev + libboost-graph1.49-dev libboost-iostreams1.49-dev libboost-python1.49-dev libboost-regex1.49-dev libboost-test1.49-dev ## Building @@ -153,11 +153,10 @@ Now that you're up and running, here are a few resources to keep in mind: - [Home page](http://ledger-cli.org) - [IRC channel](irc://irc.freenode.net/ledger) - [Mailing List / Forum](http://groups.google.com/group/ledger-cli) - - [GitHub project page](http://github.com/jwiegley/ledger) - - [Buildbot status](http://www.newartisans.com:9090) + - [GitHub project page](http://github.com/ledger/ledger) - [Ohloh code analysis](http://www.ohloh.net/projects/ledger) If you have ideas you'd like to share, the best way is either to e-mail me a patch (I prefer attachments over pasted text), or to get an account on GitHub. -Once you do, fork the [Ledger project](http://github.com/jwiegley/ledger), +Once you do, fork the [Ledger project](http://github.com/ledger/ledger), hack as much as you like, then send me a pull request via GitHub. @@ -99,14 +99,7 @@ class CommandLineApp(object): force_exit = True # If true, always ends run() with sys.exit() log_handler = None - boost_major = "1_49" - - options = { - 'debug': False, - 'verbose': False, - 'logfile': False, - 'loglevel': False - } + boost_major = "1_52" def __init__(self): "Initialize CommandLineApp." @@ -137,7 +130,8 @@ class CommandLineApp(object): op.add_option('', '--loglevel', metavar='LEVEL', type='string', action='store', dest='loglevel', default=False, help='set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL') - return + + self.options = op.get_default_values() def main(self, *args): """Main body of your application. @@ -167,7 +161,7 @@ class CommandLineApp(object): Process options and execute callback functions as needed. This method should not need to be overridden, if the main() method is defined.""" # Process the options supported and given - self.options, main_args = self.option_parser.parse_args() + self.options, main_args = self.option_parser.parse_args(values=self.options) if self.options.logfile: fh = logging.handlers.RotatingFileHandler(self.options.logfile, @@ -238,9 +232,7 @@ class PrepareBuild(CommandLineApp): self.current_ver = None #self.current_flavor = 'default' self.current_flavor = 'debug' - self.prefix_dir = None self.products_dir = None - self.build_dir = self.source_dir self.configure_args = [] self.CXXFLAGS = [] self.LDFLAGS = [] @@ -264,7 +256,7 @@ class PrepareBuild(CommandLineApp): products = self.default_products_directory() if (exists(products) and isdir(products)) or \ (exists('build') and isdir('build')): - self.build_dir = None + self.options.build_dir = None def __init__(self): CommandLineApp.__init__(self) @@ -272,8 +264,6 @@ class PrepareBuild(CommandLineApp): self.source_dir = os.getcwd() - self.initialize() - op = self.option_parser op.add_option('', '--help', action="callback", @@ -300,6 +290,9 @@ class PrepareBuild(CommandLineApp): action="store", dest="compiler", help='Use the Clang C++ compiler') + op.add_option('-N', '--ninja', action='store_true', dest='use_ninja', + default=False, + help='Use ninja to build, rather than make') op.add_option('', '--no-git', action='store_true', dest='no_git', default=False, help='Do not call out to Git; useful for offline builds') @@ -308,21 +301,25 @@ class PrepareBuild(CommandLineApp): help='Enable use of Doxygen to build ref manual ("make docs")') op.add_option('', '--python', action='store_true', dest='python', default=False, - help='Enable Python support (if disabled in acprep)') - op.add_option('', '--no-python', action='store_true', dest='no_python', - default=False, - help='Do not enable Python support by default') + help='Enable Python support') + op.add_option('', '--no-python', action='store_false', dest='python', + help='Disable python support (default)') op.add_option('', '--prefix', metavar='DIR', action="store", dest="prefix_dir", help='Use custom installation prefix') op.add_option('', '--products', metavar='DIR', action="store", dest="option_products", help='Collect all build products in this directory') op.add_option('', '--output', metavar='DIR', action="store", + default=self.source_dir, dest="build_dir", help='Build in the specified directory') op.add_option('', '--local', action="callback", callback=self.option_local, help='Build directly within the source tree (default)') + self.options = op.get_default_values() + + self.initialize() + def main(self, *args): if args and args[0] in ['default', 'debug', 'opt', 'gcov', 'gprof']: self.current_flavor = args[0] @@ -390,8 +387,8 @@ class PrepareBuild(CommandLineApp): ######################################################################### def prefix_directory(self): - if self.prefix_dir: - return self.prefix_dir + if self.options.prefix_dir: + return self.options.prefix_dir else: return None @@ -412,10 +409,10 @@ class PrepareBuild(CommandLineApp): return self.products_dir def build_directory(self): - if not self.build_dir: - self.build_dir = join(self.products_directory(), - self.current_flavor) - return self.build_dir + if not self.options.build_dir: + self.options.build_dir = join(self.products_directory(), + self.current_flavor) + return self.options.build_dir def ensure(self, dirname): if not exists(dirname): @@ -658,50 +655,26 @@ class PrepareBuild(CommandLineApp): def setup_for_johnw(self): self.configure_args.append('-DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON') - self.configure_args.append('-DBoost_USE_MULTITHREADED:BOOL=OFF') if not self.options.compiler: - self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=/usr/local/stow/clang-3.1/bin/clang++') - - self.CXXFLAGS.append('-Qunused-arguments') - self.CXXFLAGS.append('-nostdlibinc') - self.CXXFLAGS.append('-isystem') - self.CXXFLAGS.append('/usr/local/include/c++/v1') - self.CXXFLAGS.append('-isystem') - self.CXXFLAGS.append('/usr/include') - self.CXXFLAGS.append('-stdlib=libc++') - self.CXXFLAGS.append('-Wl,/usr/local/lib/libc++.dylib') - self.CXXFLAGS.append('-Wno-disabled-macro-expansion') + self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=/usr/local/bin/clang++') if self.current_flavor == 'opt': - self.configure_args.append('-DCMAKE_CXX_FLAGS:STRING=-O4') - self.configure_args.append('-DCMAKE_CXX_LINK_FLAGS:STRING=-O4') - - self.configure_args.append('-DCMAKE_INCLUDE_PATH:STRING=/usr/local/include;/opt/local/include') - self.configure_args.append('-DCMAKE_LIBRARY_PATH:STRING=/usr/local/lib;/opt/local/lib') - - self.configure_args.append('-DBOOST_ROOT=/usr/local') - self.configure_args.append('-DBOOST_INCLUDEDIR=/usr/local/include/boost-1_49') - self.configure_args.append('-DBoost_COMPILER=-clang-darwin') - - self.configure_args.append(self.source_dir) + self.configure_args.append('-DCMAKE_CXX_FLAGS_RELEASE:STRING=-O3') + self.configure_args.append('-DCMAKE_EXE_LINKER_FLAGS:STRING=-O3') + self.configure_args.append('-DCMAKE_SHARED_LINKER_FLAGS:STRING=-O3') + self.configure_args.append('-DCMAKE_MODULE_LINKER_FLAGS:STRING=-O3') + #else: + # self.CXXFLAGS.append('-g -O1 -faddress-sanitizer') + # self.LDFLAGS.append('-g -O1 -faddress-sanitizer') - elif self.options.compiler == "icc": - self.configure_args.append('-DCMAKE_AR:PATH=/opt/intel/bin/xiar') - self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=/opt/intel/bin/icc') - if self.current_flavor == 'opt': - self.configure_args.append('-DCMAKE_CXX_FLAGS:STRING=-fast') - self.configure_args.append('-DCMAKE_CXX_LINK_FLAGS:STRING=-fast') - self.configure_args.append('-DCMAKE_INCLUDE_PATH:STRING=/opt/local/include') - self.configure_args.append('-DCMAKE_LIBRARY_PATH:STRING=/opt/local/lib') - self.configure_args.append('-DBOOST_ROOT=/opt/local') self.configure_args.append(self.source_dir) else: self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=' + self.options.compiler) - self.configure_args.append('-DCMAKE_INCLUDE_PATH:STRING=/opt/local/include') - self.configure_args.append('-DCMAKE_LIBRARY_PATH:STRING=/opt/local/lib') - self.configure_args.append('-DBOOST_ROOT=/opt/local') + self.configure_args.append('-DCMAKE_INCLUDE_PATH:STRING=/usr/local/include') + self.configure_args.append('-DCMAKE_LIBRARY_PATH:STRING=/usr/local/lib') + self.configure_args.append('-DBOOST_ROOT=/usr/local') self.configure_args.append(self.source_dir) def setup_for_system(self): @@ -712,8 +685,9 @@ class PrepareBuild(CommandLineApp): self.configure_args.append('-DUSE_DOXYGEN=1') if self.options.python: self.configure_args.append('-DUSE_PYTHON=1') - if self.options.no_python: - self.configure_args.remove('-DUSE_PYTHON=1') + + if self.options.use_ninja: + self.configure_args.append('-GNinja') if exists('/Users/johnw/Projects/ledger/plan/TODO'): self.setup_for_johnw() @@ -760,7 +734,7 @@ class PrepareBuild(CommandLineApp): def option_local(self, option=None, opt_str=None, value=None, parser=None): self.log.debug('Saw option --local') - self.build_dir = self.source_dir + self.options.build_dir = self.source_dir def option_help(self, option=None, opt_str=None, value=None, parser=None): self.phase_help() @@ -844,7 +818,7 @@ class PrepareBuild(CommandLineApp): self.options.boost_include) if self.prefix_directory(): - conf_args.append('-DCMAKE_PREFIX_PATH=%s' % self.prefix_directory()) + conf_args.append('-DCMAKE_INSTALL_PREFIX=%s' % self.prefix_directory()) return (environ, conf_args + self.configure_args) @@ -861,7 +835,7 @@ class PrepareBuild(CommandLineApp): try: os.chdir(build_dir) - need_to_config = not isfile('Makefile') + need_to_config = not isfile('rules.ninja' if self.options.use_ninja else 'Makefile') if need_to_config: self.log.debug('Source => ' + self.source_dir) self.log.debug('Build => ' + build_dir) @@ -900,14 +874,13 @@ class PrepareBuild(CommandLineApp): make_args = [] for arg in args: - if arg.startswith('--'): + if arg.startswith('--') or arg.startswith('-D'): config_args.append(arg) else: make_args.append(arg) if self.options.jobs > 1 and self.current_flavor != 'gcov': make_args.append('-j%d' % self.options.jobs) - make_args.append('ARGS=-j%d' % self.options.jobs) if self.options.verbose: make_args.append('VERBOSE=1') @@ -923,18 +896,29 @@ class PrepareBuild(CommandLineApp): self.log.debug('Changing directory to ' + build_dir) os.chdir(build_dir) - self.execute(*(['make'] + make_args)) + self.execute(*(['ninja' if self.options.use_ninja else 'make'] + + make_args)) finally: os.chdir(self.source_dir) def phase_check(self, *args): self.log.info('Executing phase: update') - self.phase_make(*(['check'] + list(args))) + build_dir = self.ensure(self.build_directory()) + try: + self.log.debug('Changing directory to ' + build_dir) + os.chdir(build_dir) + + make_args = list(args) + if self.options.jobs > 1: + make_args.append('-j%d' % self.options.jobs) + + self.execute(*(['ctest'] + list(make_args))) + finally: + os.chdir(self.source_dir) def phase_update(self, *args): self.log.info('Executing phase: update') self.phase_pull() - #self.phase_check(*args) self.phase_make(*args) ######################################################################### @@ -955,10 +939,10 @@ class PrepareBuild(CommandLineApp): ######################################################################### def configure_flavor(self, flavor, reset=True): - self.initialize() # reset everything - self.build_dir = None # use the build/ tree + self.initialize() # reset everything self.current_flavor = flavor - self.prefix_dir = None + self.options.build_dir = None # use the build/ tree + self.options.prefix_dir = None if reset and exists(self.build_directory()) and \ isdir(self.build_directory()): @@ -1083,12 +1067,17 @@ typical user: submodule Updates Git submodules (better to use 'pull') version Output current HEAD version to version.m4 -NOTE: If you wish to pass options to configure or make, add "--" followed by -your options. Here are some real-world examples: +NOTE: If you wish to pass options to CMake or make, add "--" followed by +your options. Those starting with "-D" or "--" will be passed on to CMake, +positional arguments and other options will be passed to make. +For the 'config' and 'configure' phase everything will be passed to CMake. + +Here are some real-world examples: ./acprep - ./acprep opt -- make -j3 - ./acprep --enable-doxygen""" + ./acprep --python + ./acprep opt make + ./acprep make doc -- -DBUILD_WEB_DOCS=1""" sys.exit(0) PrepareBuild().run() diff --git a/contrib/non-profit-audit-reports/GPLv3 b/contrib/non-profit-audit-reports/GPLv3 new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/contrib/non-profit-audit-reports/GPLv3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/contrib/non-profit-audit-reports/LICENSE b/contrib/non-profit-audit-reports/LICENSE new file mode 100644 index 00000000..c8c63924 --- /dev/null +++ b/contrib/non-profit-audit-reports/LICENSE @@ -0,0 +1,14 @@ +Contents under contrib/non-profit-audit-reports/ are licensed GPLv3-or-later. + +The GPLv3-or-later licensing of the contents of this directory does not, +to our knowledge and belief, impact the licensing of any other part of +Ledger. Parts of the files herein are likely derivative works of the rest +of Ledger, but these works are under this subdirectory are not, to our +knowledge, used, imported, included, copied, etc. into other parts of the +codebase. + +In short, this is a small application written to use Ledger like a +library, particularly via its Python API interface. It derives from +Ledger, but Ledger does not, to our knowledge, derive from it. + +We are not lawyers and this is not legal advice. diff --git a/contrib/non-profit-audit-reports/README b/contrib/non-profit-audit-reports/README new file mode 100644 index 00000000..b4897f21 --- /dev/null +++ b/contrib/non-profit-audit-reports/README @@ -0,0 +1,100 @@ +README + +This document provides backround on the enclosed example + +Demo +---- +To run the demo do +./demo.sh + +Which should generate the following files in tests/ + chart-of-accounts.txt + general-ledger.txt + general-ledger.csv + general-ledger.ods + +And a final, "portable" zip file with the spreadsheet in + general-ledger.zip + +It *should* be possible to copy general-ledger.zip to another system, +unzip it, open general-ledger.ods in Libre Office and have the relative +links resolve correctly. + +NOTE: Export to PDF should also work. + + +Known Dependencies +------------------ +ledger (3.0) +python (2.x) +zip +libdate-manip-perl +libmath-gmp-perl + + +Temporary Hacks +--------------- +Due to an urgent project deadline the ooolib2 directory +represents some fixes to: + http://ooolib.sourceforge.net/ + +The proper version of this library can be installed on Debian systems with +# apt-get install python-ooolib + +Compare the deltas to the current version with +# diff -u /usr/share/pyshared/ooolib/__init__.py ooolib2/__init__.py + +Note also that the csv2ods.py treats columns 4 and 5 (numbering from 1) of the csv +magically. If column 4 contains a non-empty string which is not 'Receipt' +then it is interpreted as a relative path of an artifact to link to. +Similary for column 5 and 'Invoice'. + + +Sample PDF files +---------------- +The sample PDF files were created as follows: + +paps --font="Courier 12" --paper letter --top-margin=18 tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.txt | ps2pdf - tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf + +paps --font="Courier 12" --paper letter --top-margin=18 tests/Financial/Invoices/Invoice20110510.txt | ps2pdf - tests/Financial/Invoices/Invoice20110510.pdf + + +Resources +--------- +ooolib + http://ooolib.sourceforge.net/ + +LIBPF + probably does not replace ooolib + http://wp.libpf.com/?p=82 + +Libre Office Calc Guide (contains function reference) + https://www.libreoffice.org/get-help/documentation/ + +Libre Office API + http://api.libreoffice.org/examples/examples.html + http://api.libreoffice.org/examples/DevelopersGuide/examples.html + +OpenOffice Developers Guide + http://wiki.openoffice.org/wiki/Documentation/DevGuide/OpenOffice.org_Developers_Guide + +Spreadsheet Documents + http://wiki.openoffice.org/wiki/Documentation/DevGuide/Spreadsheets/Spreadsheet_Documents + +How to correctly create ODF documents using zip +(Do NOT do this, use ooolib instead) + http://www.jejik.com/articles/2010/03/how_to_correctly_create_odf_documents_using_zip/ + +Line Breaks + fo:break-before="page" + http://books.evc-cit.info/oobook/ch03.html#page-content-section + +ODF Validator + http://opendocumentfellowship.com/validator + +Editing Hyperlinks + http://help.libreoffice.org/Common/Editing_Hyperlinks + +Perl OODoc +NOTE: a replacement for POD, not ooolib + http://search.cpan.org/dist/OpenOffice-OODoc/ diff --git a/contrib/non-profit-audit-reports/bank-reconcilation.plx b/contrib/non-profit-audit-reports/bank-reconcilation.plx new file mode 100755 index 00000000..7a8da911 --- /dev/null +++ b/contrib/non-profit-audit-reports/bank-reconcilation.plx @@ -0,0 +1,232 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Math::BigFloat; +use Date::Manip; +use Data::PowerSet; + +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); +my $ONE_HUNDRED = Math::BigFloat->new("100.00"); + +my $VERBOSE = 1; +my $DEBUG = 0; + +my $LEDGER_BIN = "/usr/local/bin/ledger"; + +###################################################################### +sub BruteForceSubSetSumSolver ($$$) { + my($numberList, $totalSought, $extractNumber) = @_; + + my($P, $N) = (0, 0); + my $size = scalar(@{$numberList}); + my %Q; + my(@L) = + map { { val => &$extractNumber($_), obj => $_ } } @{$numberList}; + + my $powerset = Data::PowerSet->new(@L); + + while (my $set = $powerset->next) { + my $total = $ZERO; + foreach my $ee (@{$set}) { + $total += $ee->{val}; + } + if ($totalSought == $total) { + my(@list) = map { $_->{obj} } @{$set}; + return (1, \@list); + } + } + return (0, []); +} +###################################################################### +sub DynamicProgrammingSubSetSumSolver ($$$) { + my($numberList, $totalSought, $extractNumber) = @_; + + my($P, $N) = (0, 0); + my $size = scalar(@{$numberList}); + my %Q; + my(@L) = + map { { val => &$extractNumber($_), obj => $_ } } @{$numberList}; + + print STDERR " TotalSought:", $totalSought if $VERBOSE; + print STDERR " L in this iteration:\n [" if $VERBOSE; + + foreach my $ee (@L) { + if ($ee->{val} < 0) { + $N += $ee->{val} + } else { + $P += $ee->{val}; + } + print STDERR $ee->{val}, ", " if $VERBOSE; + } + print STDERR "]\n P = $P, N = $N\n" if ($VERBOSE); + + for (my $ii = 0 ; $ii <= $size ; $ii++ ) { + $Q{$ii}{0}{value} = 1; + $Q{$ii}{0}{list} = []; + } + for (my $jj = $N; $jj <= $P ; $jj++) { + $Q{0}{$jj}{value} = ($L[0]{val} == $jj); + $Q{0}{$jj}{list} = $Q{0}{$jj}{value} ? [ $L[0]{obj} ] : []; + } + for (my $ii = 1; $ii <= $size ; $ii++ ) { + for (my $jj = $N; $jj <= $P ; $jj++) { + if ($Q{$ii-1}{$jj}{value}) { + $Q{$ii}{$jj}{value} = 1; + + $Q{$ii}{$jj}{list} = [] unless defined $Q{$ii}{$jj}{list}; + push(@{$Q{$ii}{$jj}{list}}, @{$Q{$ii-1}{$jj}{list}}); + + } elsif ($L[$ii]{val} == $jj) { + $Q{$ii}{$jj}{value} = 1; + + $Q{$ii}{$jj}{list} = [] unless defined $Q{$ii}{$jj}{list}; + push(@{$Q{$ii}{$jj}{list}}, $jj); + } elsif ($Q{$ii-1}{$jj - $L[$ii]{val}}{value}) { + $Q{$ii}{$jj}{value} = 1; + $Q{$ii}{$jj}{list} = [] unless defined $Q{$ii}{$jj}{list}; + push(@{$Q{$ii}{$jj}{list}}, $L[$ii]{obj}, @{$Q{$ii-1}{$jj - $L[$ii]{val}}{list}}); + } else { + $Q{$ii}{$jj}{value} = 0; + $Q{$ii}{$jj}{list} = []; + } + } + } + foreach (my $ii = 0; $ii <= $size; $ii++) { + foreach (my $jj = $N; $jj <= $P; $jj++) { + print "Q($ii, $jj) == $Q{$ii}{$jj}{value} with List of ", join(", ", @{$Q{$ii}{$jj}{list}}), "\n"; + } + } + return [ $Q{$size}{$totalSought}{value}, \@{$Q{$size}{$totalSought}{list}}]; +} +###################################################################### +sub Commify ($) { + my $text = reverse $_[0]; + $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; + return scalar reverse $text; +} +###################################################################### +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} +###################################################################### +sub ConvertTwoDigitPrecisionToInteger ($) { + return sprintf("%d", $_[0] * $ONE_HUNDRED); +} +###################################################################### +sub ConvertTwoDigitPrecisionToIntegerInEntry ($) { + return ConvertTwoDigitPrecisionToInteger($_[0]->{amount}); +} +###################################################################### +my $firstArg = shift @ARGV; + +my $solver = \&BruteForceSubSetSumSolver; + +if (@ARGV < 7) { + print STDERR "usage: $0 [-d] <TITLE> <ACCOUNT_REGEX> <END_DATE> <START_SEARCH_FROM_DATE> <END_SEARCH_TO_DATE> <BANK_STATEMENT_BALANCE> <LEDGER_OPTIONS>\n"; + exit 1; +} +if ($firstArg eq '-d') { + $solver = \&DynamicProgrammingSubSetSumSolver; +} else { + unshift(@ARGV, $firstArg); +} +my($title, $account, $endDate, $startSearchFromDate, $endSearchToDate, $bankBalance, @mainLedgerOptions) = @ARGV; + +$bankBalance = ParseNumber($bankBalance); + +my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-e', $endDate, '-F', '%t\n', 'bal', "/$account/"); + +open(FILE, "-|", @fullCommand) or die "unable to run command ledger command: @fullCommand: $!"; + +my $total; +foreach my $line (<FILE>) { + chomp $line; + die "Unable to parse output line from: \"$line\"" + unless $line =~ /^\s*\$\s*([\-\d\.\,]+)\s*$/ and not defined $total; + $total = $1; + $total = ParseNumber($total); +} +close FILE; +if (not defined $total or $? != 0) { + die "unable to run ledger @fullCommand: $!"; +} +my $differenceSought = $total - $bankBalance; + +my $err; +my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), + "%Y-%m-%d"); +die "Date calculation error on $endDate" if ($err); + +my $earliestStartDate = DateCalc(ParseDate($endDate), ParseDateDelta("- 1 month"), \$err); + +die "Date calculation error on $endDate" if ($err); + +my $startDate = ParseDate($startSearchFromDate); + +my @solution; +while ($startDate ge $earliestStartDate) { + $startDate = DateCalc(ParseDate($startDate), ParseDateDelta("- 1 day"), \$err); + die "Date calculation error on $endDate" if ($err); + + my $formattedStartDate = UnixDate($startDate, "%Y-%m-%d"); + + print STDERR "Testing $formattedStartDate through $endSearchToDate for a total of ", Commify($differenceSought), ": \n" + if $VERBOSE; + + my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $formattedStartDate, '-e', $endSearchToDate, + '-F', '"%(date)","%C","%P","%t"\n', + 'reg', "/$account/"); + + open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + + my @entries; + + foreach my $line (<FILE>) { + die "Unable to parse output line from: $line" + unless $line =~ /^\s*"([^"]*)","([^"]*)","([^"]*)","([^"]*)"\s*$/; + my($date, $checkNum, $payee, $amount) = ($1, $2, $3, $4); + die "$amount is not a valid amount" + unless $amount =~ s/\s*\$\s*([\-\d\.\,]+)\s*$/$1/; + $amount = ParseNumber($amount); + + push(@entries, { date => $date, checkNum => $checkNum, + payee => $payee, amount => $amount }); + } + close FILE; + die "unable to properly run ledger command: @fullCommand: $!" unless ($? == 0); + + @solution = $solver->(\@entries, + ConvertTwoDigitPrecisionToInteger($differenceSought), + \&ConvertTwoDigitPrecisionToIntegerInEntry); + if ($VERBOSE) { + if ($solution[0]) { + use Data::Dumper; + print STDERR "Solution for $formattedStartDate to $formattedEndDate, $differenceSought: \n", + Data::Dumper->Dump(\@solution); + } else { + print STDERR "No Solution Found. :(\n"; + } + } + last if ($solution[0]); +} +if ($solution[0]) { + print "\"title:$formattedEndDate: $title\"\n\"BANK RECONCILATION: $account\",\"ENDING\",\"$formattedEndDate\"\n"; + print "\n\n\"DATE\",\"CHECK NUM\",\"PAYEE\",\"AMOUNT\"\n\n"; + print "\"$formattedEndDate\",\"\",\"BANK ACCOUNT BALANCE\",\"\$$bankBalance\"\n\n"; + foreach my $ee (sort { $a->{date} cmp $b->{date} } @{$solution[1]}) { + print "\"$ee->{date}\",\"$ee->{checkNum}\",\"$ee->{payee}\",\"\$$ee->{amount}\"\n"; + } + print "\n\"$formattedEndDate\",\"\",\"OUR ACCOUNT BALANCE\",\"\$$total\"\n\n"; +} +############################################################################### +# +# Local variables: +# compile-command: "perl -c bank-reconcilation.plx" +# End: diff --git a/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx b/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx new file mode 100755 index 00000000..6234542c --- /dev/null +++ b/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx @@ -0,0 +1,204 @@ +#!/usr/bin/perl +# cash-receipts-and-disbursments-journals -*- Perl -*- +# +# Script to generate a cash receipts and disbursement joural reports +# using Ledger. +# +# Accountants sometimes ask for a report called the "cash receipts and +# disbursements journals". From a programmer's perspective, these are two +# reports that have the following properties: +# +# * Receipts: "a list of all transactions in the period where funds +# enter a cash account (i.e., the amount reconciled +# against the cash account is > 0" +# +# * Disbursements: "a list of all transactions in the period where +# funds leave a cash account (i.e., the amount +# reconciled against the cash account is < 0) +# +# Copyright (C) 2011, 2012, 2013 Bradley M. Kuhn +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + + +use strict; +use warnings; + +use Math::BigFloat; +use Date::Manip; +use File::Temp qw/tempfile/; + +my $LEDGER_CMD = "/usr/local/bin/ledger"; + +my $ACCT_WIDTH = 75; + +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} + +sub LedgerAcctToFilename($) { + my $x = $_[0]; + $x =~ s/ /-/g; + $x =~ s/:/-/g; + return $x; +} + +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); + +if (@ARGV < 2) { + print STDERR "usage: $0 <BEGIN_DATE> <END_DATE> <OTHER_LEDGER_OPTS>\n"; + exit 1; +} + +my($beginDate, $endDate, @otherLedgerOpts) = @ARGV; + +my(@chartOfAccountsOpts) = ('-V', '-F', "%150A\n", '-w', '-s', + '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg'); + +open(CHART_DATA, "-|", $LEDGER_CMD, @chartOfAccountsOpts) + or die "Unable to run $LEDGER_CMD @chartOfAccountsOpts: $!"; + +my @accounts; +while (my $line = <CHART_DATA>) { + chomp $line; + next if $line =~ /^\s*\<\s*Adjustment\s*\>\s*$/; + next if $line =~ /^Equity:/; # Stupid auto-account made by ledger. + $line =~ s/^\s*//; $line =~ s/\s*$//; + push(@accounts, $line); + +} +close(CHART_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0; + +my $formattedEndDate = new Date::Manip::Date; +die "badly formatted end date, $endDate" if $formattedEndDate->parse($endDate); +my $oneDayLess = new Date::Manip::Delta; +die "bad one day less" if $oneDayLess->parse("- 1 day"); +$formattedEndDate = $formattedEndDate->calc($oneDayLess); +$formattedEndDate = $formattedEndDate->printf("%Y/%m/%d"); + +foreach my $typeData ({ name => 'disbursements', query => 'a<=0' }, + { name => 'receipts', query => 'a>0' }) { + my $fileNameBase = $typeData->{name}; + + open(CSV_OUT, ">", "$fileNameBase.csv") or die "unable to open $fileNameBase.csv: $!"; + + foreach my $acct (sort { $a cmp $b } @accounts) { + next unless ($acct =~ /^(?:Assets|Liabilities)/); + + my @entryLedgerOpts = ('-l', $typeData->{query}, + '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'print', $acct); + + open(ENTRY_DATA, "-|", $LEDGER_CMD, @entryLedgerOpts) + or die "Unable to run $LEDGER_CMD @entryLedgerOpts: $!"; + + my($tempFH, $tempFile) = tempfile("cashreportsXXXXXXXX", TMPDIR => 1); + + while (my $line = <ENTRY_DATA>) { print $tempFH $line; } + close(ENTRY_DATA); die "Error reading ledger output for entries: $!" unless $? == 0; + $tempFH->close() or die "Error writing ledger output for entries to temp file, $tempFile: $!"; + + goto SKIP_REGISTER_COMMANDS if (-z $tempFile); + + print CSV_OUT "\"ACCOUNT:\",\"$acct\"\n\"PERIOD START:\",\"$beginDate\"\n\"PERIOD END:\",\"$formattedEndDate\"\n"; + print CSV_OUT '"DATE","CHECK NUM","NAME","ACCOUNT","AMOUNT"'; + + my $formatString = '\n"%(date)","%C","%P","%A","%t"'; + my $tagStrings = ""; + foreach my $tagField (qw/Receipt Invoice Statement Contract PurchaseOrder Approval Check IncomeDistributionAnalysis CurrencyRate/) { + print CSV_OUT ',"', $tagField, '"'; + $tagStrings .= ',"link:%(tag(\'' . $tagField . '\'))"'; + } + $formatString .= $tagStrings . '\n%/"","","","%A","%t"' . $tagStrings . '\n'; + + # I thought '--sort', 'd', '--sort-xact', 'a', should + # have worked below for a good sort. Then I tried + # rather than '--sort', "d,n,a", which didn't work either. + # I opened a bug: http://bugs.ledger-cli.org/show_bug.cgi?id=901 + + my @csvRegLedgerOpts = ('-f', $tempFile, '-V', '-F', $formatString, '-w', '--sort', 'd', + '-b', $beginDate, '-e', $endDate, 'reg'); + + open(CSV_DATA, "-|", $LEDGER_CMD, @csvRegLedgerOpts) + or die "unable to run ledger command for $fileNameBase.csv: $!"; + + my($curDepositDate, $curDepositTotal); + + while (my $line = <CSV_DATA>) { + $line =~ s/"link:"/""/g; + + # Skip lines that have Adjustment or Equity: in them. + next if $line =~ + /^\s*"[^"]*","[^"]*","[^"]*","(\s*\<\s*Adjustment\s*\>\s*|Equity:)/; + + # Note that we don't do our usual "$TWO_CENTS" check on Adjustment + # here. That's by design: if we consistently ignore Adjustements in + # the same way, it might have the appearance that a Superman + # III/Office Space -style movement of funds is going on. By just + # straight "ignoring" them here, and not doing the TWO_CENTS test, it + # helps to assure that. + + # However, it's worth noting that the ignoring of "Adjustment" in these + # scripts is not that meaningful and doesn't indicate as Superman + # III/Office Space -style scheme, because such a scheme would also have + # to be implemented in the main Ledger codebase. + + + my $date = $line; chomp $date; + $date =~ s/^\s*"([^"]*)"\s*,.*$/$1/; + if (defined $date and $date !~ /^\s*$/ and + defined $curDepositDate and ($date ne $curDepositDate or + ($date eq $curDepositDate and $line !~ /DEPOSIT[\s\-]+BRANCH/))) { + print CSV_OUT "\"$curDepositDate\",\"SUBTOTAL\",\"BRANCH DEPOSIT TOTAL:\",\"\",\"\$$curDepositTotal\"\n\n"; + $curDepositTotal = $curDepositDate = undef; + } + if ($line =~ /DEPOSIT[\s\-]+BRANCH/) { + if (not defined $curDepositDate) { + $curDepositDate = $line; chomp $curDepositDate; + $curDepositDate =~ s/^\s*"([^"]+)"\s*,.*$/$1/; + } + } + # This is a bit of a hack because I can't ssume that the line with the + # description on it has the account name in it. + if (defined $curDepositDate and $line =~ /$acct/) { + my $amt = $line; + chomp $amt; + $amt =~ s/^\s*"[^"]*","[^"]*","[^"]*","[^"]*","\$\s*([^"]*)".*$/$1/; + $amt =~ s/,//g; + + $curDepositTotal = 0.0 unless defined $curDepositTotal; + $curDepositTotal += $amt; + } + print CSV_OUT $line; + } + # Catch potential last Deposit subtotal + print CSV_OUT "\n\"$curDepositDate\",\"SUBTOTAL\",\"BRANCH DEPOSIT TOTAL:\",\"\",\"\$$curDepositTotal\"\n\n" + if (defined $curDepositDate); + + close(CSV_DATA); die "Error read from csv ledger command $!" unless $? == 0; + print CSV_OUT "pagebreak\n"; + SKIP_REGISTER_COMMANDS: + unlink($tempFile); + } + close(CSV_OUT); die "Error read write csv out to $fileNameBase.csv: $!" unless $? == 0; +} +############################################################################### +# +# Local variables: +# compile-command: "perl -c cash-receipts-and-disbursments-journals.plx" +# End: + diff --git a/contrib/non-profit-audit-reports/csv2ods.py b/contrib/non-profit-audit-reports/csv2ods.py new file mode 100755 index 00000000..6aabcb59 --- /dev/null +++ b/contrib/non-profit-audit-reports/csv2ods.py @@ -0,0 +1,233 @@ +#!/usr/bin/python +# csv2ods.py +# Convert example csv file to ods +# +# Copyright (c) 2012 Tom Marble +# Copyright (c) 2012, 2013 Bradley M. Kuhn +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +import sys, os, os.path, optparse +import csv +import ooolib2 +import shutil +import string +from Crypto.Hash import SHA256 + +def err(msg): + print 'error: %s' % msg + sys.exit(1) + +def ReadChecksums(inputFile): + checksums = {} + with open(inputFile, "r") as inputFH: + entries = inputFH.readlines() + for ee in entries: + fileName, checksum = ee.split(":") + fileName = fileName.replace(' ', "") + checksum = checksum.replace(' ', "") + checksum = checksum.replace("\n", "") + checksums[checksum] = fileName + return checksums + +def ChecksumFile(filename): + sha256 = SHA256.new() + chunk_size = 8192 + with open(filename, 'rb') as myFile: + while True: + chunk = myFile.read(chunk_size) + if len(chunk) == 0: + break + sha256.update(chunk) + return sha256.hexdigest() + +def main(): + program = os.path.basename(sys.argv[0]) + + print get_file_checksum(sys.argv[1]) + +def csv2ods(csvname, odsname, encoding='', singleFileDirectory=None, knownChecksums={}, verbose = False): + filesSavedinManifest = {} + + if knownChecksums: + checksumCache = {} + + if verbose: + print 'converting from %s to %s' % (csvname, odsname) + + if singleFileDirectory: + if not os.path.isdir(os.path.join(os.getcwd(),singleFileDirectory)): + os.mkdir(singleFileDirectory) + + doc = ooolib2.Calc() + # add a pagebreak style + style = 'pagebreak' + style_pagebreak = doc.styles.get_next_style('row') + style_data = tuple([style, ('style:row-height', doc.styles.property_row_height)]) + doc.styles.style_config[style_data] = style_pagebreak + # add a currency style + style = 'currency' + style_currency = doc.styles.get_next_style('cell') + style_data = tuple([style]) + doc.styles.style_config[style_data] = style_currency + + row = 1 + csvdir = os.path.dirname(csvname) + if len(csvdir) == 0: + csvdir = '.' + csvfile = open(csvname, 'rb') + reader = csv.reader(csvfile, delimiter=',', quotechar='"') + for fields in reader: + if len(fields) > 0: + for col in range(len(fields)): + val = fields[col] + if encoding != '' and val[0:5] != "link:": # Only utf8 encode if it's not a filename + val = unicode(val, 'utf8') + if len(val) > 0 and val[0] == '$': + doc.set_cell_value(col + 1, row, 'currency', val[1:]) + else: + if (len(val) > 0 and val[0:5] == "link:"): + val = val[5:] + linkname = os.path.basename(val) # name is just the last component + newFile = None + + if not singleFileDirectory: + newFile = val + + if knownChecksums: + if not checksumCache.has_key(val): + checksum = ChecksumFile(val) + checksumCache[val] = checksum + else: + checksum = checksumCache[val] + + if knownChecksums.has_key(checksum): + newFile = knownChecksums[checksum] + print "FOUND new file in known: " + newFile + + if not newFile: + relativeFileWithPath = os.path.basename(val) + + fileName, fileExtension = os.path.splitext(relativeFileWithPath) + newFile = fileName[:15] # 15 is an arbitrary choice. + newFile = newFile + fileExtension + # We'll now test to see if we made this file + # before, and if it matched the same file we + # now want. If it doesn't, try to make a + # short file name for it. + if filesSavedinManifest.has_key(newFile) and filesSavedinManifest[newFile] != val: + testFile = None + for cc in list(string.letters) + list(string.digits): + testFile = cc + newFile + if not filesSavedinManifest.has_key(testFile): + break + testFile = None + if not testFile: + raise Exception("too many similar file names for linkage; giving up") + else: + newFile = testFile + if not os.path.exists(csvdir + '/' + val): + raise Exception("File" + csvdir + '/' + val + " does not exist in single file directory mode; giving up") + src = os.path.join(csvdir, val) + dest = os.path.join(csvdir, singleFileDirectory, newFile) + shutil.copyfile(src, dest) + shutil.copystat(src, dest) + shutil.copymode(src, dest) + + newFile = os.path.join(singleFileDirectory, newFile) + + if knownChecksums: + checksumCache[checksum] = newFile + knownChecksums[checksum] = newFile + + linkrel = '../' + newFile # ../ means remove the name of the *.ods + doc.set_cell_value(col + 1, row, 'link', (linkrel, linkname)) + linkpath = csvdir + '/' + val + + if not val in filesSavedinManifest: + filesSavedinManifest[newFile] = val + + if not os.path.exists(linkpath): + print "WARNING: link %s DOES NOT EXIST at %s" % (val, linkpath) + if verbose: + if os.path.exists(linkpath): + print 'relative link %s EXISTS at %s' % (val, linkpath) + else: + if val == "pagebreak": + doc.sheets[doc.sheet_index].set_sheet_config(('row', row), style_pagebreak) + else: + if val[0:6] == "title:": + doc.sheets[doc.sheet_index].set_name(val[6:]) + else: + doc.set_cell_value(col + 1, row, 'string', val) + else: + # enter an empty string for blank lines + doc.set_cell_value(1, row, 'string', '') + row += 1 + # save manifest file + if filesSavedinManifest.keys() != []: + manifestFH = open("MANIFEST", "a") + manifestFH.write("# Files from %s\n" % odsname) + for file in filesSavedinManifest.keys(): + manifestFH.write("%s\n" % file) + + manifestFH.close() + # Save spreadsheet file. + doc.save(odsname) + +def main(): + program = os.path.basename(sys.argv[0]) + version = '0.1' + parser = optparse.OptionParser(usage='%prog [--help] [--verbose]', + version='%prog ' + version) + parser.add_option('-v', '--verbose', action='store_true', + dest='verbose', + help='provide extra information while processing') + parser.add_option('-c', '--csv', action='store', + help='csv file to process') + parser.add_option('-o', '--ods', action='store', + help='ods output filename') + parser.add_option('-e', '--encoding', action='store', + help='unicode character encoding type') + parser.add_option('-d', '--single-file-directory', action='store', + help='directory name to move all files into') + parser.add_option('-s', '--known-checksum-list', action='store', + help='directory name to move all files into') + (options, args) = parser.parse_args() + + if len(args) != 0: + parser.error("not expecting extra args") + if not os.path.exists(options.csv): + err('csv does not exist: %s' % options.csv) + if not options.ods: + (root, ext) = os.path.splitext(options.csv) + options.ods = root + '.ods' + if options.verbose: + print '%s: verbose mode on' % program + print 'csv:', options.csv + print 'ods:', options.ods + print 'ods:', options.encoding + if options.known_checksum_list and not options.single_file_directory: + err(program + ": --known-checksum-list option is completely useless without --single-file-directory") + knownChecksums = {} + if options.known_checksum_list: + if not os.access(options.known_checksum_list, os.R_OK): + err(program + ": unable to read file: " + options.known_checksum_list) + knownChecksums = ReadChecksums(options.known_checksum_list) + csv2ods(options.csv, options.ods, options.encoding, options.single_file_directory, knownChecksums, options.verbose) + +if __name__ == '__main__': + main() diff --git a/contrib/non-profit-audit-reports/demo.sh b/contrib/non-profit-audit-reports/demo.sh new file mode 100755 index 00000000..a4b837a6 --- /dev/null +++ b/contrib/non-profit-audit-reports/demo.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# demo.sh +# Demonstrate a non-profit GL export and conversion to ODS + +program=$(basename $0) +dir=$(dirname $0) +cd $dir +dir=$(pwd -P) +export PYTHONPATH=$dir/ooolib2 + +getcsv=$dir/general-ledger-report.plx +csv2ods=$dir/csv2ods.py + +echo "Demonstrating ledger to ODS export in $dir/tests" +cd $dir/tests +sampledata=non-profit-test-data.ledger +echo " based on the sample data in $sampledata" + +$getcsv 2011/03/01 2012/03/01 -f $sampledata +if [ -e general-ledger.csv ]; then + echo "data was exported to: general-ledger.csv" +else + echo "error creating csv file" + exit 1 +fi + +$csv2ods --verbose --csv general-ledger.csv +if [ -e general-ledger.ods ]; then + echo "csv was converted to: general-ledger.ods" +else + echo "error creating ods file" + exit 1 +fi + +echo general-ledger.ods >> MANIFEST + +# create a portable zip file with the spreadsheet +# and the linked artifacts + +echo creating portable zipfile... +cat MANIFEST | zip -@ ../general-ledger.zip + +echo " " +echo "created general-ledger.zip" + diff --git a/contrib/non-profit-audit-reports/fund-report.plx b/contrib/non-profit-audit-reports/fund-report.plx new file mode 100755 index 00000000..ce59da96 --- /dev/null +++ b/contrib/non-profit-audit-reports/fund-report.plx @@ -0,0 +1,235 @@ +#!/usr/bin/perl +# fund-report.plx -*- Perl -*- +# +# Script to generate a Restricted Fund Report. Usefulness of this +# script may be confined to those who track separate funds in their +# accounts by having accounts that match this format: +# /^(Income|Expenses|Unearned Income|(Accrued:[^:]+:):PROJECTNAME/ + +# Conservancy does this because we carefully track fund balances for our +# fiscal sponsored projects. Those who aren't fiscal sponsors won't find +# this report all that useful, I suspect. Note that the name +# "Conservancy" is special-cased in a few places, mainly because our +# "General" fund is called "Conservancy". + +# +# Copyright (C) 2011, 2012, 2013 Bradley M. Kuhn +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use Math::BigFloat; +use Date::Manip; + +my $LEDGER_CMD = "/usr/local/bin/ledger"; + +my $ACCT_WIDTH = 70; + +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); +my $TWO_CENTS = Math::BigFloat->new("0.02"); + +if (@ARGV < 2) { + print STDERR "usage: $0 <START_DATE> <END_DATE> <LEDGER_OPTIONS>\n"; + exit 1; +} +my($startDate, $endDate, @mainLedgerOptions) = @ARGV; + +my $err; +my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), + "%Y/%m/%d"); +die "Date calculation error on $endDate" if ($err); +my $formattedStartDate = UnixDate(ParseDate($startDate), "%Y/%m/%d"); +die "Date calculation error on $startDate" if ($err); + +# First, get balances for starting and ending for each fund + +my %funds; + +foreach my $type ('starting', 'ending') { + my(@ledgerOptions) = (@mainLedgerOptions, + '-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-s'); + + if ($type eq 'starting') { + push(@ledgerOptions, '-e', $startDate); + } else { + push(@ledgerOptions,'-e', $endDate); + } + push(@ledgerOptions, 'reg', '/^(Income|Expenses):([^:]+):/'); + + open(LEDGER_FUNDS, "-|", $LEDGER_CMD, @ledgerOptions) + or die "Unable to run $LEDGER_CMD @ledgerOptions: $!"; + + while (my $fundLine = <LEDGER_FUNDS>) { + die "Unable to parse output line from first funds command: \"$fundLine\"" + unless $fundLine =~ /^\s*([^\$]+)\s+\$\s*\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\<Adjustment\>/ and (abs($amount) <= $TWO_CENTS); + die "Weird account found, $account with amount of $amount in command: @ledgerOptions\n" + unless $account =~ s/^\s*(?:Income|Expenses):([^:]+)://; + $account = $1; + $account = 'General' if $account eq 'Conservancy'; # FIXME: this is a special case for Consrevancy + $funds{$account}{$type} += $amount; + } + close LEDGER_FUNDS; + die "Failure on ledger command @ledgerOptions: $!" unless ($? == 0); +} +foreach my $fund (keys %funds) { + foreach my $type (keys %{$funds{$fund}}) { + $funds{$fund}{$type} = $ZERO - $funds{$fund}{$type}; + } +} +my(@ledgerOptions) = (@mainLedgerOptions, + '-V', '-X', '$', '-F', "%-.70A %22.108t\n", '-w', '-s', + '-b', $startDate, '-e', $endDate, 'reg'); + +my @possibleTypes = ('Income', 'Expenses', 'Unearned Income', 'Retained Earnings', 'Retained Costs', + 'Accrued:Loans Receivable', 'Accrued:Accounts Payable', + 'Accrued:Accounts Receivable', 'Accrued:Expenses'); + +foreach my $type (@possibleTypes) { + foreach my $fund (keys %funds) { + my $query; + $query = ($fund eq 'General') ? "/^${type}:Conservancy/": "/^${type}:$fund/"; + open(LEDGER_INCOME, "-|", $LEDGER_CMD, @ledgerOptions, $query) + or die "Unable to run $LEDGER_CMD for funds: $!"; + $funds{$fund}{$type} = $ZERO; + while (my $line = <LEDGER_INCOME>) { + die "Unable to parse output line from $type line command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $funds{$fund}{$type} += $amount; + } + close LEDGER_INCOME; + die "Failure on ledger command for ${type}:$fund: $!" unless ($? == 0); + } +} + +my %tot; +($tot{Start}, $tot{End}) = ($ZERO, $ZERO); + +my %beforeEndings = ('Income' => 1, 'Expenses' => 1); +my %afterEndings; + +# For other @possibleTypes, build up @fieldsList to just thoes that are present. + +foreach my $fund (keys %funds) { + foreach my $type (@possibleTypes) { + if ($funds{$fund}{$type} != $ZERO) { + if ($type =~ /^(Unearned Income|Accrued)/) { + $afterEndings{$type} = 1; + } else { + $beforeEndings{$type} = 1; + } + } + } +} +my(@beforeEndingFields, @afterEndingFields); + +foreach my $ii (@possibleTypes) { + push(@beforeEndingFields, $ii) if defined $beforeEndings{$ii}; + push(@afterEndingFields, $ii) if defined $afterEndings{$ii}; +} +# Make sure fieldLists present items are zero for those that should be zero. +foreach my $fund (keys %funds) { + foreach my $type ('starting', @beforeEndingFields, 'ending', @afterEndingFields) { + $funds{$fund}{$type} = $ZERO unless defined $funds{$fund}{$type}; + } +} + +print '"RESTRICTED AND GENERAL FUND REPORT",', "\"BEGINNING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n"; +print '"FUND","STARTING BALANCE",'; +my @finalPrints; +foreach my $type (@beforeEndingFields) { + $tot{$type} = $ZERO; + my $formattedType = $type; + print "\"$formattedType\","; +} +print '"ENDING BALANCE",""'; +foreach my $type (@afterEndingFields) { + $tot{$type} = $ZERO; + my $formattedType = $type; + $formattedType = "Prepaid Expenses" if $formattedType eq 'Accrued:Expenses'; + $formattedType =~ s/^Accrued://; + print ",\"$formattedType\""; +} +print "\n\n"; + +sub printTotal ($$) { + my($label, $tot) = @_; + print "\"$label\",\"\$$tot->{Start}\","; + foreach my $type (@beforeEndingFields) { + print "\"\$$tot->{$type}\","; + } + print "\"\$$tot->{End}\",\"\""; + foreach my $type (@afterEndingFields) { + print ",\"\$$tot->{$type}\""; + } + print "\n"; +} + +foreach my $fund (sort { + if ($a eq "General") { return 1 } + elsif ($b eq "General") { return -1 } + else { return $a cmp $b } } + keys %funds) { + my $sanityTotal = $funds{$fund}{starting}; + + if ($fund eq 'General') { + print "\n"; + printTotal("Restricted Subtotal", \%tot); + print "\n"; + } + $tot{Start} += $funds{$fund}{starting}; + $tot{End} += $funds{$fund}{ending}; + + print "\"$fund\",\"\$$funds{$fund}{starting}\","; + foreach my $type (@beforeEndingFields) { + print "\"\$$funds{$fund}{$type}\","; + $tot{$type} += $funds{$fund}{$type}; + } + print "\"\$$funds{$fund}{ending}\",\"\""; + foreach my $type (@afterEndingFields) { + print ",\"\$$funds{$fund}{$type}\""; + $tot{$type} += $funds{$fund}{$type}; + } + print "\n"; + # Santity check: + if (abs($funds{$fund}{ending} - + ($funds{$fund}{starting} + - $funds{$fund}{Income} - $funds{$fund}{Expenses})) + > $TWO_CENTS) { + print "$fund FAILED SANITY CHECK: Ending: $funds{$fund}{ending} \n\n\n"; + warn "$fund FAILED SANITY CHECK"; + } +} +print "\n"; +printTotal("OVERALL TOTAL", \%tot); +############################################################################### +# +# Local variables: +# compile-command: "perl -c fund-report.plx" +# End: + diff --git a/contrib/non-profit-audit-reports/general-ledger-report.plx b/contrib/non-profit-audit-reports/general-ledger-report.plx new file mode 100755 index 00000000..dce855b4 --- /dev/null +++ b/contrib/non-profit-audit-reports/general-ledger-report.plx @@ -0,0 +1,225 @@ +#!/usr/bin/perl +# general-ledger-report.plx -*- Perl -*- +# +# Script to generate a General Ledger report that accountants like +# using Ledger. +# +# Copyright (C) 2011, 2012, 2013 Bradley M. Kuhn +# Copyright (C) 2012 Tom Marble +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use Math::BigFloat; +use Date::Manip; + +my $LEDGER_CMD = "/usr/local/bin/ledger"; + +my $ACCT_WIDTH = 75; + +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} + +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); + +if (@ARGV < 3) { + print STDERR "usage: $0 <BEGIN_DATE> <END_DATE> <OTHER_LEDGER_OPTS>\n"; + exit 1; +} + + +open(MANIFEST, ">", "MANIFEST") or die "Unable to open MANIFEST for writing: $!"; + +my($beginDate, $endDate, @otherLedgerOpts) = @ARGV; + +my $formattedEndDate = new Date::Manip::Date; +die "badly formatted end date, $endDate" if $formattedEndDate->parse($endDate); +my $oneDayLess = new Date::Manip::Delta; +die "bad one day less" if $oneDayLess->parse("- 1 day"); +$formattedEndDate = $formattedEndDate->calc($oneDayLess); +$formattedEndDate = $formattedEndDate->printf("%Y/%m/%d"); + +my $formattedBeginDate = new Date::Manip::Date; +die "badly formatted end date, $endDate" if $formattedBeginDate->parse($endDate); +$formattedBeginDate = $formattedBeginDate->printf("%Y/%m/%d"); + + +my(@chartOfAccountsOpts) = ('-V', '-F', "%150A\n", '-w', '-s', + '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg'); + +open(CHART_DATA, "-|", $LEDGER_CMD, @chartOfAccountsOpts) + or die "Unable to run $LEDGER_CMD @chartOfAccountsOpts: $!"; + +my @accounts; +while (my $line = <CHART_DATA>) { + chomp $line; + next if $line =~ /^\s*\<\s*Adjustment\s*\>\s*$/; + next if $line =~ /^\s*Equity:/; # Stupid auto-account made by ledger. + $line =~ s/^\s*//; $line =~ s/\s*$//; + push(@accounts, $line); + +} +close(CHART_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0; + +open(CHART_OUTPUT, ">", "chart-of-accounts.csv") or die "unable to write chart-of-accounts.csv: $!"; +print MANIFEST "chart-of-accounts.csv\n"; + +print CHART_OUTPUT "\"CHART OF ACCOUNTS\","; +print CHART_OUTPUT "\"BEGINNING:\",\"$formattedBeginDate\","; +print CHART_OUTPUT "\"ENDING:\",\"$formattedEndDate\"\n"; + +sub preferredAccountSorting ($$) { + if ($_[0] =~ /^Assets/ and $_[1] !~ /^Assets/) { + return -1; + } elsif ($_[1] =~ /^Assets/ and $_[0] !~ /^Assets/) { + return 1; + } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^(Assets|Liabilities)/) { + return -1; + } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^(Assets|Liabilities)/) { + return 1; + } elsif ($_[0] =~ /^(Accrued:[^:]+Receivable)/ and $_[1] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { + return -1; + } elsif ($_[1] =~ /^(Accrued:[^:]+Receivable)/ and $_[0] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { + return 1; + } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { + return -1; + } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { + return 1; + } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return -1; + } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return 1; + } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { + return -1; + } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { + return 1; + } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { + return -1; + } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { + return 1; + } else { + return $_[0] cmp $_[1]; + } +} + +my @sortedAccounts; +foreach my $acct ( sort preferredAccountSorting @accounts) { + print CHART_OUTPUT "\"$acct\"\n"; + push(@sortedAccounts, $acct); +} +close(CHART_OUTPUT); die "error writing to chart-of-accounts.txt: $!" unless $? == 0; + +my %commands = ( + 'totalEnd' => [ $LEDGER_CMD, @otherLedgerOpts, '-V', '-X', '$', + '-e', $endDate, '-F', '%-.80A %22.108t\n', '-s', + 'reg' ], + 'totalBegin' => [ $LEDGER_CMD, @otherLedgerOpts, '-V', '-X', '$', + '-e', $beginDate, '-F', '%-.80A %22.108t\n', + '-s', 'reg' ]); + +my %balanceData; + +foreach my $id (keys %commands) { + my(@command) = @{$commands{$id}}; + + open(FILE, "-|", @command) or die "unable to run command ledger command: @command: $!"; + + foreach my $line (<FILE>) { + die "Unable to parse output line from balance data $id command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\<Adjustment\>/ and (abs($amount) <= 0.02); + next if $account =~ /^Equity:/; # Stupid auto-account made by ledger. + $balanceData{$id}{$account} = $amount; + } + close FILE; + die "unable to run balance data ledger command, @command: $!" unless ($? == 0); +} + +open(GL_TEXT_OUT, ">", "general-ledger.txt") or die "unable to write general-ledger.txt: $!"; +print MANIFEST "general-ledger.txt\n"; +open(GL_CSV_OUT, ">", "general-ledger.csv") or die "unable to write general-ledger.csv: $!"; +print MANIFEST "general-ledger.csv\n"; + +my %manifest; +foreach my $acct (@sortedAccounts) { + print GL_TEXT_OUT "\n\nACCOUNT: $acct\nFROM: $beginDate TO $formattedEndDate\n\n"; + my @acctLedgerOpts = ('-V', '-F', + "%(date) %-.10C %-.80P %-.80N %18t %18T\n", '-w', '--sort', 'd', + '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg', '/^' . $acct . '$/'); + open(GL_TEXT_DATA, "-|", $LEDGER_CMD, @acctLedgerOpts) + or die "Unable to run $LEDGER_CMD @acctLedgerOpts: $!"; + + foreach my $line (<GL_TEXT_DATA>) { + print GL_TEXT_OUT $line; + } + close(GL_TEXT_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0; + + print GL_CSV_OUT "\n\"ACCOUNT:\",\"$acct\"\n\"PERIOD START:\",\"$formattedBeginDate\"\n\"PERIOD END:\",\"$formattedEndDate\"\n"; + print GL_CSV_OUT '"DATE","CHECK NUM","NAME","TRANSACTION AMT","BALANCE"'; + + my $formatString = '"%(date)","%C","%P","%t",""'; + foreach my $tagField (qw/Receipt Invoice Statement Contract PurchaseOrder Approval Check IncomeDistributionAnalysis CurrencyRate/) { + print GL_CSV_OUT ',"', $tagField, '"'; + $formatString .= ',"link:%(tag(\'' . $tagField . '\'))"'; + } + $formatString .= "\n"; + print GL_CSV_OUT "\n"; + if ($acct =~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + $balanceData{totalBegin}{$acct} = $ZERO unless defined $balanceData{totalBegin}{$acct}; + print GL_CSV_OUT "\"$formattedBeginDate\"", ',"","BALANCE","","$', "$balanceData{totalBegin}{$acct}\"\n"; + } + + @acctLedgerOpts = ('-V', '-F', $formatString, '-w', '--sort', 'd', '-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'reg', '/^' . $acct . '$/'); + open(GL_CSV_DATA, "-|", $LEDGER_CMD, @acctLedgerOpts) + or die "Unable to run $LEDGER_CMD @acctLedgerOpts: $!"; + + foreach my $line (<GL_CSV_DATA>) { + $line =~ s/"link:"/""/g; + print GL_CSV_OUT $line; + next if $line =~ /ACCOUNT:.*PERIOD/; # Skip column header lines + $line =~ s/^"[^"]*","[^"]*","[^"]*","[^"]*","[^"]*",//; + while ($line =~ s/^"([^"]*)"(,|$)//) { + my $file = $1; + next if $file =~ /^\s*$/; + $file =~ s/^link:(.*)$/$1/; + warn "$file does not exist and/or is not readable" unless -r $file; + print MANIFEST "$file\n" if not defined $manifest{$file}; + $manifest{$file} = $line; + } + } + if ($acct =~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + $balanceData{totalEnd}{$acct} = $ZERO unless defined $balanceData{totalEnd}{$acct}; + print GL_CSV_OUT "\"$formattedEndDate\"", ',"","BALANCE","","$', "$balanceData{totalEnd}{$acct}\"\n"; + } + print GL_CSV_OUT "pagebreak\n"; + close(GL_CSV_DATA); die "error reading ledger output for chart of accounts: $!" unless $? == 0; +} +close(GL_TEXT_OUT); die "error writing to general-ledger.txt: $!" unless $? == 0; +close(GL_CSV_OUT); die "error writing to general-ledger.csv: $!" unless $? == 0; +############################################################################### +# +# Local variables: +# compile-command: "perl -c general-ledger-report.plx" +# End: + diff --git a/contrib/non-profit-audit-reports/ooolib2/__init__.py b/contrib/non-profit-audit-reports/ooolib2/__init__.py new file mode 100644 index 00000000..6106fc5c --- /dev/null +++ b/contrib/non-profit-audit-reports/ooolib2/__init__.py @@ -0,0 +1,1987 @@ +"ooolib-python - Copyright (C) 2006-2009 Joseph Colton" + +# ooolib-python - Python module for creating Open Document Format documents. +# Copyright (C) 2006-2009 Joseph Colton + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. + +# This library 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 +# Lesser General Public License for more details. + +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# You can contact me by email at josephcolton@gmail.com + +# Import Standard Modules +import zipfile # Needed for reading/writing documents +import time +import sys +import glob +import os +import re +import xml.parsers.expat # Needed for parsing documents + +def version_number(): + "Get the ooolib-python version number" + return "0.0.17" + +def version(): + "Get the ooolib-python version" + return "ooolib-python-%s" % version_number() + +def clean_string(data): + "Returns an XML friendly copy of the data string" + + data = unicode(data) # This line thanks to Chris Ender + + data = data.replace('&', '&') + data = data.replace("'", ''') + data = data.replace('"', '"') + data = data.replace('<', '<') + data = data.replace('>', '>') + data = data.replace('\t', '<text:tab-stop/>') + data = data.replace('\n', '<text:line-break/>') + return data + +class XML: + "XML Class - Used to convert nested lists into XML" + def __init__(self): + "Initialize ooolib XML instance" + pass + + def _xmldata(self, data): + datatype = data.pop(0) + datavalue = data.pop(0) + outstring = '%s' % datavalue + return outstring + + def _xmltag(self, data): + outstring = '' + # First two + datatype = data.pop(0) + dataname = data.pop(0) + outstring = '<%s' % dataname + # Element Section + element = 1 + while(data): + # elements + newdata = data.pop(0) + if (newdata[0] == 'element' and element): + newstring = self._xmlelement(newdata) + outstring = '%s %s' % (outstring, newstring) + continue + if (newdata[0] != 'element' and element): + element = 0 + outstring = '%s>' % outstring + if (newdata[0] == 'tag' or newdata[0] == 'tagline'): + outstring = '%s\n' % outstring + if (newdata[0] == 'tag'): + newstring = self._xmltag(newdata) + outstring = '%s%s' % (outstring, newstring) + continue + if (newdata[0] == 'tagline'): + newstring = self._xmltagline(newdata) + outstring = '%s%s' % (outstring, newstring) + continue + if (newdata[0] == 'data'): + newstring = self._xmldata(newdata) + outstring = '%s%s' % (outstring, newstring) + continue + if (element): + element = 0 + outstring = '%s>\n' % outstring + outstring = '%s</%s>\n' % (outstring, dataname) + return outstring + + def _xmltagline(self, data): + outstring = '' + # First two + datatype = data.pop(0) + dataname = data.pop(0) + outstring = '<%s' % dataname + # Element Section + while(data): + # elements + newdata = data.pop(0) + if (newdata[0] != 'element'): break + newstring = self._xmlelement(newdata) + outstring = '%s %s' % (outstring, newstring) + outstring = '%s/>\n' % outstring + # Non-Element Section should not exist + return outstring + + def _xmlelement(self, data): + datatype = data.pop(0) + dataname = data.pop(0) + datavalue = data.pop(0) + outstring = '%s="%s"' % (dataname, datavalue) + return outstring + + def convert(self, data): + """Convert nested lists into XML + + The convert method takes a nested lists and converts them + into XML to be used in Open Document Format documents. + There are three types of lists that are recognized at this + time. They are as follows: + + 'tag' - Tag opens a set of data that is eventually closed + with a similar tag. + List: ['tag', 'xml'] + XML: <xml></xml> + + 'tagline' - Taglines are similar to tags, except they open + and close themselves. + List: ['tagline', 'xml'] + XML: <xml/> + + 'element' - Elements are pieces of information stored in an + opening tag or tagline. + List: ['element', 'color', 'blue'] + XML: color="blue" + + 'data' - Data is plain text directly inserted into the XML + document. + List: ['data', 'hello'] + XML: hello + + Bring them all together for something like this. + + Lists: + ['tag', 'xml', ['element', 'a', 'b'], ['tagline', 'xml2'], + ['data', 'asdf']] + + XML: + <xml a="b"><xml2/>asdf</xml> + """ + outlines = [] + outlines.append('<?xml version="1.0" encoding="UTF-8"?>') + if (type(data) == type([]) and len(data) > 0): + if data[0] == 'tag': outlines.append(self._xmltag(data)) + return outlines + +class Meta: + "Meta Data Class" + + def __init__(self, doctype, debug=False): + self.doctype = doctype + + # Set the debug mode + self.debug = debug + + # The generator should always default to the version number + self.meta_generator = version() + self.meta_title = '' + self.meta_subject = '' + self.meta_description = '' + self.meta_keywords = [] + self.meta_creator = 'ooolib-python' + self.meta_editor = '' + self.meta_user1_name = 'Info 1' + self.meta_user2_name = 'Info 2' + self.meta_user3_name = 'Info 3' + self.meta_user4_name = 'Info 4' + self.meta_user1_value = '' + self.meta_user2_value = '' + self.meta_user3_value = '' + self.meta_user4_value = '' + self.meta_creation_date = self.meta_time() + + # Parser data + self.parser_element_list = [] + self.parser_element = "" + self.parser_count = 0 + + def set_meta(self, metaname, value): + """Set meta data in your document. + + Currently implemented metaname options are as follows: + 'creator' - The document author + """ + if metaname == 'creator': self.meta_creator = value + if metaname == 'editor': self.meta_editor = value + if metaname == 'title': self.meta_title = value + if metaname == 'subject': self.meta_subject = value + if metaname == 'description': self.meta_description = value + if metaname == 'user1name': self.meta_user1_name = value + if metaname == 'user2name': self.meta_user2_name = value + if metaname == 'user3name': self.meta_user3_name = value + if metaname == 'user4name': self.meta_user4_name = value + if metaname == 'user1value': self.meta_user1_value = value + if metaname == 'user2value': self.meta_user2_value = value + if metaname == 'user3value': self.meta_user3_value = value + if metaname == 'user4value': self.meta_user4_value = value + if metaname == 'keyword': + if value not in self.meta_keywords: + self.meta_keywords.append(value) + + def get_meta_value(self, metaname): + "Get meta data value for a given metaname." + + if metaname == 'creator': return self.meta_creator + if metaname == 'editor': return self.meta_editor + if metaname == 'title': return self.meta_title + if metaname == 'subject': return self.meta_subject + if metaname == 'description': return self.meta_description + if metaname == 'user1name': return self.meta_user1_name + if metaname == 'user2name': return self.meta_user2_name + if metaname == 'user3name': return self.meta_user3_name + if metaname == 'user4name': return self.meta_user4_name + if metaname == 'user1value': return self.meta_user1_value + if metaname == 'user2value': return self.meta_user2_value + if metaname == 'user3value': return self.meta_user3_value + if metaname == 'user4value': return self.meta_user4_value + if metaname == 'keyword': return self.meta_keywords + + def meta_time(self): + "Return time string in meta data format" + t = time.localtime() + stamp = "%04d-%02d-%02dT%02d:%02d:%02d" % (t[0], t[1], t[2], t[3], t[4], t[5]) + return stamp + + def parse_start_element(self, name, attrs): + if self.debug: print '* Start element:', name + self.parser_element_list.append(name) + self.parser_element = self.parser_element_list[-1] + + # Need the meta name from the user-defined tags + if (self.parser_element == "meta:user-defined"): + self.parser_count += 1 + # Set user-defined name + self.set_meta("user%dname" % self.parser_count, attrs['meta:name']) + + # Debugging statements + if self.debug: print " List: ", self.parser_element_list + if self.debug: print " Attributes: ", attrs + + + def parse_end_element(self, name): + if self.debug: print '* End element:', name + if name != self.parser_element: + print "Tag Mismatch: '%s' != '%s'" % (name, self.parser_element) + self.parser_element_list.pop() + + # Readjust parser_element_list and parser_element + if (self.parser_element_list): + self.parser_element = self.parser_element_list[-1] + else: + self.parser_element = "" + + def parse_char_data(self, data): + if self.debug: print " Character data: ", repr(data) + + # Collect Meta data fields + if (self.parser_element == "dc:title"): + self.set_meta("title", data) + if (self.parser_element == "dc:description"): + self.set_meta("description", data) + if (self.parser_element == "dc:subject"): + self.set_meta("subject", data) + if (self.parser_element == "meta:initial-creator"): + self.set_meta("creator", data) + + # Try to maintain the same creation date + if (self.parser_element == "meta:creation-date"): + self.meta_creation_date = data + + # The user defined fields need to be kept track of, parser_count does that + if (self.parser_element == "meta:user-defined"): + self.set_meta("user%dvalue" % self.parser_count, data) + + def meta_parse(self, data): + "Parse Meta Data from a meta.xml file" + + # Debugging statements + if self.debug: + # Sometimes it helps to see the document that was read from + print data + print "\n\n\n" + + # Create parser + parser = xml.parsers.expat.ParserCreate() + # Set up parser callback functions + parser.StartElementHandler = self.parse_start_element + parser.EndElementHandler = self.parse_end_element + parser.CharacterDataHandler = self.parse_char_data + + # Actually parse the data + parser.Parse(data, 1) + + def get_meta(self): + "Generate meta.xml file data" + self.meta_date = self.meta_time() + self.data = ['tag', 'office:document-meta', + ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'], + ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'], + ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'], + ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'], + ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'], + ['element', 'office:version', '1.0'], + ['tag', 'office:meta', + ['tag', 'meta:generator', # Was: 'OpenOffice.org/2.0$Linux OpenOffice.org_project/680m5$Build-9011' + ['data', self.meta_generator]], # Generator is set the the ooolib-python version. + ['tag', 'dc:title', + ['data', self.meta_title]], # This data is the document title + ['tag', 'dc:description', + ['data', self.meta_description]], # This data is the document description + ['tag', 'dc:subject', + ['data', self.meta_subject]], # This data is the document subject + ['tag', 'meta:initial-creator', + ['data', self.meta_creator]], # This data is the document creator + ['tag', 'meta:creation-date', + ['data', self.meta_creation_date]], # This is the original creation date of the document + ['tag', 'dc:creator', + ['data', self.meta_editor]], # This data is the document editor + ['tag', 'dc:date', + ['data', self.meta_date]], # This is the last modified date of the document + ['tag', 'dc:language', + ['data', 'en-US']], # We will probably always use en-US for language + ['tag', 'meta:editing-cycles', + ['data', '1']], # Edit cycles will probably always be 1 for generated documents + ['tag', 'meta:editing-duration', + ['data', 'PT0S']], # Editing duration is modified - creation date + ['tag', 'meta:user-defined', + ['element', 'meta:name', self.meta_user1_name], + ['data', self.meta_user1_value]], + ['tag', 'meta:user-defined', + ['element', 'meta:name', self.meta_user2_name], + ['data', self.meta_user2_value]], + ['tag', 'meta:user-defined', + ['element', 'meta:name', self.meta_user3_name], + ['data', self.meta_user3_value]], + ['tag', 'meta:user-defined', + ['element', 'meta:name', self.meta_user4_name], + ['data', self.meta_user4_value]]]] +# ['tagline', 'meta:document-statistic', +# ['element', 'meta:table-count', len(self.sheets)], # len(self.sheets) ? +# ['element', 'meta:cell-count', '15']]]] # Not sure how to keep track + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + + + +class CalcStyles: + "Calc Style Management - Used to keep track of created styles." + + def __init__(self): + self.style_config = {} + # Style Counters + self.style_table = 1 + self.style_column = 1 + self.style_row = 1 + self.style_cell = 1 + # Style Properties (Defaults) - To be used later + self.property_column_width_default = '0.8925in' # Default Column Width + self.property_row_height_default = '0.189in' # Default Row Height + # Set Defaults + self.property_column_width = '0.8925in' # Default Column Width + self.property_row_height = '0.189in' # Default Row Height + self.property_cell_bold = False # Bold off be default + self.property_cell_italic = False # Italic off be default + self.property_cell_underline = False # Underline off be default + self.property_cell_fg_color = 'default' # Text Color Default + self.property_cell_bg_color = 'default' # Cell Background Default + self.property_cell_bg_image = 'none' # Cell Background Default + self.property_cell_fontsize = '10' # Cell Font Size Default + self.property_cell_valign = 'default' # Vertial Alignment Default + self.property_cell_halign = 'default' # Horizantal Alignment Default + + def get_next_style(self, style): + "Returns the next style code for the given style" + style_code = "" + if style == 'table': + style_code = 'ta%d' % self.style_table + self.style_table+=1 + if style == 'column': + style_code = 'co%d' % self.style_column + self.style_column+=1 + if style == 'row': + style_code = 'ro%d' % self.style_row + self.style_row+=1 + if style == 'cell': + style_code = 'ce%d' % self.style_cell + self.style_cell+=1 + return style_code + + def set_property(self, style, name, value): + "Sets a property which will later be turned into a code" + if style == 'table': + pass + if style == 'column': + if name == 'style:column-width': self.property_column_width = value + if style == 'row': + if name == 'style:row-height': self.property_row_height = value + if style == 'cell': + if name == 'bold' and type(value) == type(True): self.property_cell_bold = value + if name == 'italic' and type(value) == type(True): self.property_cell_italic = value + if name == 'underline' and type(value) == type(True): self.property_cell_underline = value + if name == 'fontsize': self.property_cell_fontsize = value + if name == 'color': + self.property_cell_fg_color = 'default' + redata = re.search("^(#[\da-fA-F]{6})$", value) + if redata: self.property_cell_fg_color = value.lower() + if name == 'background': + self.property_cell_bg_color = 'default' + redata = re.search("^(#[\da-fA-F]{6})$", value) + if redata: self.property_cell_bg_color = value.lower() + if name == 'backgroundimage': + self.property_cell_bg_image = value + if name == 'valign': + self.property_cell_valign = value + if name == 'halign': + self.property_cell_halign = value + + def get_style_code(self, style): + style_code = "" + if style == 'table': + style_code = "ta1" + if style == 'column': + style_data = tuple([style, + ('style:column-width', self.property_column_width)]) + if style_data in self.style_config: + # Style Exists, return code + style_code = self.style_config[style_data] + else: + # Style does not exist, create code and return it + style_code = self.get_next_style(style) + self.style_config[style_data] = style_code + if style == 'row': + style_data = tuple([style, + ('style:row-height', self.property_row_height)]) + if style_data in self.style_config: + # Style Exists, return code + style_code = self.style_config[style_data] + else: + # Style does not exist, create code and return it + style_code = self.get_next_style(style) + self.style_config[style_data] = style_code + if style == 'cell': + style_data = [style] + # Add additional styles + if self.property_cell_bold: style_data.append(('bold', True)) + if self.property_cell_italic: style_data.append(('italic', True)) + if self.property_cell_underline: style_data.append(('underline', True)) + if self.property_cell_fontsize != '10': + style_data.append(('fontsize', self.property_cell_fontsize)) + if self.property_cell_fg_color != 'default': + style_data.append(('color', self.property_cell_fg_color)) + if self.property_cell_bg_color != 'default': + style_data.append(('background', self.property_cell_bg_color)) + if self.property_cell_bg_image != 'none': + style_data.append(('backgroundimage', self.property_cell_bg_image)) + if self.property_cell_valign != 'default': + style_data.append(('valign', self.property_cell_valign)) + if self.property_cell_halign != 'default': + style_data.append(('halign', self.property_cell_halign)) + + style_data = tuple(style_data) + if style_data in self.style_config: + # Style Exists, return code + style_code = self.style_config[style_data] + else: + # Style does not exist, create code and return it + style_code = self.get_next_style(style) + self.style_config[style_data] = style_code + return style_code + + def get_automatic_styles(self): + "Return 'office:automatic-styles' lists" + automatic_styles = ['tag', 'office:automatic-styles'] + + for style_data in self.style_config: + style_code = self.style_config[style_data] + style_data = list(style_data) + style = style_data.pop(0) + + if style == 'column': + style_list = ['tag', 'style:style', + ['element', 'style:name', style_code], # Column 'co1' properties + ['element', 'style:family', 'table-column']] + tagline = ['tagline', 'style:table-column-properties', + ['element', 'fo:break-before', 'auto']] # unsure what break before means + + for set in style_data: + name, value = set + if name == 'style:column-width': + tagline.append(['element', 'style:column-width', value]) + style_list.append(tagline) + automatic_styles.append(style_list) + + if style == 'row': + style_list = ['tag', 'style:style', + ['element', 'style:name', style_code], # Column 'ro1' properties + ['element', 'style:family', 'table-row']] + tagline = ['tagline', 'style:table-row-properties'] + + for set in style_data: + name, value = set + if name == 'style:row-height': + tagline.append(['element', 'style:row-height', value]) + tagline.append(['element', 'fo:break-before', 'auto']) +# tagline.append(['element', 'style:use-optimal-row-height', 'true']) # Overrides settings + style_list.append(tagline) + automatic_styles.append(style_list) + + if style == 'pagebreak': + style_list = ['tag', 'style:style', + ['element', 'style:name', style_code], # Column 'ro1' properties + ['element', 'style:family', 'table-row']] + tagline = ['tagline', 'style:table-row-properties'] + + for set in style_data: + name, value = set + if name == 'style:row-height': + tagline.append(['element', 'style:row-height', value]) + tagline.append(['element', 'fo:break-before', 'page']) +# tagline.append(['element', 'style:use-optimal-row-height', 'true']) # Overrides settings + style_list.append(tagline) + automatic_styles.append(style_list) + + if style == 'cell': + style_list = ['tag', 'style:style', + ['element', 'style:name', style_code], # ce1 style + ['element', 'style:family', 'table-cell'], # cell + ['element', 'style:parent-style-name', 'Default']] # parent is Default + # hack for currency + if style_code == 'ce1': + style_list.append(['element', + 'style:data-style-name', + 'N104']) + + # Cell Properties + tagline = ['tag', 'style:table-cell-properties'] + tagline_additional = [] + for set in style_data: + name, value = set + if name == 'background': + tagline.append(['element', 'fo:background-color', value]) + if name == 'backgroundimage': + tagline.append(['element', 'fo:background-color', 'transparent']) + # Additional tags added later + bgimagetag = ['tagline', 'style:background-image'] + bgimagetag.append(['element', 'xlink:href', value]) + bgimagetag.append(['element', 'xlink:type', 'simple']) + bgimagetag.append(['element', 'xlink:actuate', 'onLoad']) + tagline_additional.append(bgimagetag) + if name == 'valign': + if value in ['top', 'bottom', 'middle']: + tagline.append(['element', 'style:vertical-align', value]) + if name == 'halign': + tagline.append(['element', 'style:text-align-source', 'fix']) + if value in ['filled']: + tagline.append(['element', 'style:repeat-content', 'true']) + else: + tagline.append(['element', 'style:repeat-content', 'false']) + + # Add any additional internal tags + while tagline_additional: + tagadd = tagline_additional.pop(0) + tagline.append(tagadd) + + style_list.append(tagline) + + # Paragraph Properties + tagline = ['tagline', 'style:paragraph-properties'] + tagline_valid = False + for set in style_data: + name, value = set + if name == 'halign': + tagline_valid = True + if value in ['center']: + tagline.append(['element', 'fo:text-align', 'center']) + if value in ['end', 'right']: + tagline.append(['element', 'fo:text-align', 'end']) + if value in ['start', 'filled', 'left']: + tagline.append(['element', 'fo:text-align', 'start']) + if value in ['justify']: + tagline.append(['element', 'fo:text-align', 'justify']) + # Conditionally add the tagline + if tagline_valid: style_list.append(tagline) + + + # Text Properties + tagline = ['tagline', 'style:text-properties'] + for set in style_data: + name, value = set + if name == 'bold': + tagline.append(['element', 'fo:font-weight', 'bold']) + if name == 'italic': + tagline.append(['element', 'fo:font-style', 'italic']) + if name == 'underline': + tagline.append(['element', 'style:text-underline-style', 'solid']) + tagline.append(['element', 'style:text-underline-width', 'auto']) + tagline.append(['element', 'style:text-underline-color', 'font-color']) + if name == 'color': + tagline.append(['element', 'fo:color', value]) + if name == 'fontsize': + tagline.append(['element', 'fo:font-size', '%spt' % value]) + style_list.append(tagline) + + automatic_styles.append(style_list) + + + # Attach ta1 style + automatic_styles.append(['tag', 'style:style', + ['element', 'style:name', 'ta1'], + ['element', 'style:family', 'table'], + ['element', 'style:master-page-name', 'Default'], + ['tagline', 'style:table-properties', + ['element', 'table:display', 'true'], + ['element', 'style:writing-mode', 'lr-tb']]]) + + + return automatic_styles + + + +class CalcSheet: + "Calc Sheet Class - Used to keep track of the data for an individual sheet." + + def __init__(self, sheetname): + "Initialize a sheet" + self.sheet_name = sheetname + self.sheet_values = {} + self.sheet_config = {} + self.max_col = 0 + self.max_row = 0 + + def get_sheet_dimensions(self): + "Returns the max column and row" + return (self.max_col, self.max_row) + + def clean_formula(self, data): + "Returns a formula for use in ODF" + # Example Translations + # '=SUM(A1:A2)' + # datavalue = 'oooc:=SUM([.A1:.A2])' + # '=IF((A5>A4);A4;"")' + # datavalue = 'oooc:=IF(([.A5]>[.A4]);[.A4];"")' + data = str(data) + data = clean_string(data) + redata = re.search('^=([A-Z]+)(\(.*)$', data) + if redata: + # funct is the function name. The rest if the string will be the functArgs + funct = redata.group(1) + functArgs = redata.group(2) + # Search for cell lebels and replace them + reList = re.findall('([A-Z]+\d+)', functArgs) + # sort and keep track so we do not do a cell more than once + reList.sort() + lastVar = '' + while reList: + # Replace each cell label + curVar = reList.pop() + if curVar == lastVar: continue + lastVar = curVar + functArgs = functArgs.replace(curVar, '[.%s]' % curVar) + data = 'oooc:=%s%s' % (funct, functArgs) + return data + + def get_name(self): + "Returns the sheet name" + return self.sheet_name + + def set_name(self, sheetname): + "Resets the sheet name" + self.sheet_name = sheetname + + def get_sheet_values(self): + "Returns the sheet cell values" + return self.sheet_values + + def get_sheet_value(self, col, row): + "Get the value contents of a cell" + cell = (col, row) + if cell in self.sheet_values: + return self.sheet_values[cell] + else: + return None + + def get_sheet_config(self): + "Returns the sheet cell properties" + return self.sheet_config + + def set_sheet_config(self, location, style_code): + "Sets Style Code for a given location" + self.sheet_config[location] = style_code + + def set_sheet_value(self, cell, datatype, datavalue): + """Sets the value for a specific cell + + cell must be in the format (col, row) where row and col are int. + Example: B5 would be written as (2, 5) + datatype must be one of 'string', 'float', 'formula', 'currency' + datavalue should be a string + """ + # Catch invalid data + if type(cell) != type(()) or len(cell) != 2: + print "Invalid Cell" + return + (col, row) = cell + if type(col) != type(1): + print "Invalid Cell" + return + if type(row) != type(1): + print "Invalid Cell" + return + # Fix String Data + if datatype in ['string', 'annotation']: + datavalue = clean_string(datavalue) + # Fix Link Data. Link's value is a tuple containing (url, description) + if (datatype == 'link'): + url = clean_string(datavalue[0]) + desc = clean_string(datavalue[1]) + datavalue = (url, desc) + # Fix Formula Data + if datatype == 'formula': + datavalue = self.clean_formula(datavalue) + # Adjust maximum sizes + if col > self.max_col: self.max_col = col + if row > self.max_row: self.max_row = row + datatype = str(datatype) + if (datatype not in ['string', 'float', 'currency', 'formula', 'annotation', 'link']): + # Set all unknown cell types to string + datatype = 'string' + datavalue = str(datavalue) + + # The following lines are taken directly from HPS + # self.sheet_values[cell] = (datatype, datavalue) + # HPS: Cell content is now a list of tuples instead of a tuple + # While storing here, store the cell contents first and the annotation next. While generating the XML reverse this + contents = self.sheet_values.get(cell, {'annotation':None,'link':None, 'value':None}) + if datatype == 'annotation': + contents['annotation'] = (datatype, datavalue) + elif datatype == 'link': + contents['link'] = (datatype, datavalue) + else: + contents['value'] = (datatype, datavalue) + + self.sheet_values[cell] = contents + + + def get_lists(self): + "Returns nested lists for XML processing" + if (self.max_col == 0 and self.max_row == 0): + sheet_lists = ['tag', 'table:table', + ['element', 'table:name', self.sheet_name], # Set the Sheet Name + ['element', 'table:style-name', 'ta1'], + ['element', 'table:print', 'false'], + ['tagline', 'table:table-column', + ['element', 'table:style-name', 'co1'], + ['element', 'table:default-cell-style-name', 'Default']], + ['tag', 'table:table-row', + ['element', 'table:style-name', 'ro1'], + ['tagline', 'table:table-cell']]] + else: + # Base Information + sheet_lists = ['tag', 'table:table', + ['element', 'table:name', self.sheet_name], # Set the sheet name + ['element', 'table:style-name', 'ta1'], + ['element', 'table:print', 'false']] + +# ['tagline', 'table:table-column', +# ['element', 'table:style-name', 'co1'], +# ['element', 'table:number-columns-repeated', self.max_col], # max_col? '2' +# ['element', 'table:default-cell-style-name', 'Default']], + + # Need to add column information + for col in range(1, self.max_col+1): + location = ('col', col) + style_code = 'co1' + if location in self.sheet_config: + style_code = self.sheet_config[location] + sheet_lists.append(['tagline', 'table:table-column', + ['element', 'table:style-name', style_code], + ['element', 'table:default-cell-style-name', 'Default']]) + + + # Need to create each row + for row in range(1, self.max_row + 1): + location = ('row', row) + style_code = 'ro1' + if location in self.sheet_config: + style_code = self.sheet_config[location] + rowlist = ['tag', 'table:table-row', + ['element', 'table:style-name', style_code]] + for col in range(1, self.max_col + 1): + cell = (col, row) + style_code = 'ce1' # Default all cells to ce1 + if cell in self.sheet_config: + style_code = self.sheet_config[cell] # Lookup cell if available + if cell in self.sheet_values: + # (datatype, datavalue) = self.sheet_values[cell] # Marked for removal + collist = ['tag', 'table:table-cell'] + if style_code != 'ce1': + collist.append(['element', 'table:style-name', style_code]) + + # Contents, annotations, and links added by HPS + contents = self.sheet_values[cell] # cell contents is a dictionary + if contents['value']: + (datatype, datavalue) = contents['value'] + if datatype == 'float': + collist.append(['element', 'office:value-type', datatype]) + collist.append(['element', 'office:value', datavalue]) + if datatype == 'currency': + collist.append(['element', 'table:style-name', "ce1"]) + collist.append(['element', 'office:value-type', datatype]) + collist.append(['element', 'office:currency', 'USD']) + collist.append(['element', 'office:value', datavalue]) + + if datatype == 'string': + collist.append(['element', 'office:value-type', datatype]) + if datatype == 'formula': + collist.append(['element', 'table:formula', datavalue]) + collist.append(['element', 'office:value-type', 'float']) + collist.append(['element', 'office:value', '0']) + datavalue = '0' + else: + datavalue = None + + if contents['annotation']: + (annotype, annoval) = contents['annotation'] + collist.append(['tag', 'office:annotation', + ['tag', 'text:p', ['data', annoval]]]) + + if contents['link']: + (linktype, linkval) = contents['link'] + if datavalue: + collist.append(['tag', 'text:p', ['data', datavalue], + ['tag', 'text:a', ['element', 'xlink:href', linkval[0]], + ['data', linkval[1]]]]) + else: # no value; just fill the link + collist.append(['tag', 'text:p', + ['tag', 'text:a', ['element', 'xlink:href', linkval[0]], + ['data', linkval[1]]]]) + else: + if datavalue: + collist.append(['tag', 'text:p', ['data', datavalue]]) + + + + else: + collist = ['tagline', 'table:table-cell'] + rowlist.append(collist) + sheet_lists.append(rowlist) + return sheet_lists + +class Calc: + "Calc Class - Used to create OpenDocument Format Calc Spreadsheets." + def __init__(self, sheetname=None, opendoc=None, debug=False): + "Initialize ooolib Calc instance" + # Default to no debugging + self.debug = debug + if not sheetname: sheetname = "Sheet1" + self.sheets = [CalcSheet(sheetname)] # The main sheet will be initially called 'Sheet1' + self.sheet_index = 0 # We initially start on the first sheet + self.styles = CalcStyles() + self.meta = Meta('ods') + self.styles.get_style_code('column') # Force generation of default column + self.styles.get_style_code('row') # Force generation of default row + self.styles.get_style_code('table') # Force generation of default table + self.styles.get_style_code('cell') # Force generation of default cell + self.manifest_files = [] # List of extra files included + self.manifest_index = 1 # Index of added manifest files + + # Data Parsing + self.parser_element_list = [] + self.parser_element = "" + self.parser_sheet_num = 0 + self.parser_sheet_row = 0 + self.parser_sheet_column = 0 + self.parser_cell_repeats = 0 + self.parser_cell_string_pending = False + self.parser_cell_string_line = "" + + # See if we need to read a document + if opendoc: + # Verify that the document exists + if self.debug: print "Opening Document: %s" % opendoc + + # Okay, now we load the file + self.load(opendoc) + + def debug_level(self, level): + """Set debug level: + True if you want debugging messages + False if you do not. + """ + self.debug = level + + def file_mimetype(self, filename): + "Determine the filetype from the filename" + parts = filename.lower().split('.') + ext = parts[-1] + if (ext == 'png'): return (ext, "image/png") + if (ext == 'gif'): return (ext, "image/gif") + return (ext, "image/unknown") + + def add_file(self, filename): + """Prepare a file for loading into ooolib + + The filename should be the local filesystem name for + the file. The file is then prepared to be included in + the creation of the final document. The file needs to + remain in place so that it is available when the actual + document creation happens. + """ + # mimetype set to (ext, filetype) + mimetype = self.file_mimetype(filename) + newname = "Pictures/%08d.%s" % (self.manifest_index, mimetype[0]) + self.manifest_index += 1 + filetype = mimetype[1] + self.manifest_files.append((filename, filetype, newname)) + return newname + + def set_meta(self, metaname, value): + "Set meta data in your document." + self.meta.set_meta(metaname, value) + + def get_meta_value(self, metaname): + "Get meta data value for a given metaname" + return self.meta.get_meta_value(metaname) + + def get_sheet_name(self): + "Returns the sheet name" + return self.sheets[self.sheet_index].get_name() + + def get_sheet_dimensions(self): + "Returns the sheet dimensions in (cols, rows)" + return self.sheets[self.sheet_index].get_sheet_dimensions() + + def set_column_property(self, column, name, value): + "Set Column Properties" + if name == 'width': + # column number column needs column-width set to value + self.styles.set_property('column', 'style:column-width', value) + style_code = self.styles.get_style_code('column') + self.sheets[self.sheet_index].set_sheet_config(('col', column), style_code) + + def set_row_property(self, row, name, value): + "Set row Properties" + if name == 'height': + # row number row needs row-height set to value + self.styles.set_property('row', 'style:row-height', value) + style_code = self.styles.get_style_code('row') + self.sheets[self.sheet_index].set_sheet_config(('row', row), style_code) + + def set_cell_property(self, name, value): + """Turn and off cell properties + + Actual application of properties is handled by setting a value.""" + # background images need to be handled a little differently + # because they need to also be inserted into the final document + if (name == 'backgroundimage'): + # Add file and modify value + value = self.add_file(value) + self.styles.set_property('cell', name, value) + + def get_sheet_index(self): + "Return the current sheet index number" + return self.sheet_index + + def set_sheet_index(self, index): + "Set the sheet index" + if type(index) == type(1): + if index >= 0 and index < len(self.sheets): + self.sheet_index = index + return self.sheet_index + + def get_sheet_count(self): + "Returns the number of existing sheets" + return len(self.sheets) + + def new_sheet(self, sheetname): + "Create a new sheet" + self.sheet_index = len(self.sheets) + self.sheets.append(CalcSheet(sheetname)) + return self.sheet_index + + def set_cell_value(self, col, row, datatype, value): + "Set the value for a given cell" + self.sheets[self.sheet_index].set_sheet_value((col, row), datatype, value) + style_code = self.styles.get_style_code('cell') + self.sheets[self.sheet_index].set_sheet_config((col, row), style_code) + + def get_cell_value(self, col, row): + "Get a cell value tuple (type, value) for a given cell" + sheetvalue = self.sheets[self.sheet_index].get_sheet_value(col, row) + # We stop here if there is no value for sheetvalue + if sheetvalue == None: return sheetvalue + # Now check to see if we have a value tuple + if 'value' in sheetvalue: + return sheetvalue['value'] + else: + return None + + def load(self, filename): + """Load .ods spreadsheet. + + The load function loads data from a document into the current cells. + """ + # Read in the important files + + # meta.xml + data = self._zip_read(filename, "meta.xml") + self.meta.meta_parse(data) + + # content.xml + data = self._zip_read(filename, "content.xml") + self.content_parse(data) + + # settings.xml - I do not remember putting anything here + # styles.xml - I do not remember putting anything here + + def parse_content_start_element(self, name, attrs): + if self.debug: print '* Start element:', name + self.parser_element_list.append(name) + self.parser_element = self.parser_element_list[-1] + + # Keep track of the current sheet number + if (self.parser_element == 'table:table'): + # Move to starting cell + self.parser_sheet_row = 0 + self.parser_sheet_column = 0 + # Increment the sheet number count + self.parser_sheet_num += 1 + if (self.parser_sheet_num - 1 != self.sheet_index): + # We are not on the first sheet and need to create a new sheet. + # We will automatically move to the new sheet + sheetname = "Sheet%d" % self.parser_sheet_num + if 'table:name' in attrs: sheetname = attrs['table:name'] + self.new_sheet(sheetname) + else: + # We are on the first sheet and will need to overwrite the default name + sheetname = "Sheet%d" % self.parser_sheet_num + if 'table:name' in attrs: sheetname = attrs['table:name'] + self.sheets[self.sheet_index].set_name(sheetname) + + # Update the row numbers + if (self.parser_element == 'table:table-row'): + self.parser_sheet_row += 1 + self.parser_sheet_column = 0 + + # Okay, now keep track of the sheet cell data + if (self.parser_element == 'table:table-cell'): + # By default it will repeat zero times + self.parser_cell_repeats = 0 + # We must be in a new column + self.parser_sheet_column += 1 + # Set some default values + datatype = "" + value = "" + # Get values from attrs hash + if 'office:value-type' in attrs: datatype = attrs['office:value-type'] + if 'office:value' in attrs: value = attrs['office:value'] + if 'table:formula' in attrs: + datatype = 'formula' + value = attrs['table:formula'] + if datatype == 'string': + datatype = "" + self.parser_cell_string_pending = True + self.parser_cell_string_line = "" + if 'table:number-columns-repeated' in attrs: + self.parser_cell_repeats = int(attrs['table:number-columns-repeated']) - 1 + # Set the cell value + if datatype: + # I should do this once per cell repeat above 0 + for i in range(0, self.parser_cell_repeats+1): + self.set_cell_value(self.parser_sheet_column+i, self.parser_sheet_row, datatype, value) + + # There are lots of interesting cases with table:table-cell data. One problem is + # reading the number of embedded spaces correctly. This code should help us get + # the number of spaces out. + + if (self.parser_element == 'text:s'): + # This means we have a number of spaces + count_num = 0 + if 'text:c' in attrs: + count_alpha = attrs['text:c'] + if (count_alpha.isdigit()): + count_num = int(count_alpha) + # I am not sure what to do if we do not have a string pending + if (self.parser_cell_string_pending == True): + # Append the currect number of spaces to the end + self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, ' '*count_num) + + if (self.parser_element == 'text:tab-stop'): + if (self.parser_cell_string_pending == True): + self.parser_cell_string_line = "%s\t" % (self.parser_cell_string_line) + + if (self.parser_element == 'text:line-break'): + if (self.parser_cell_string_pending == True): + self.parser_cell_string_line = "%s\n" % (self.parser_cell_string_line) + + # Debugging statements + if self.debug: print " List: ", self.parser_element_list + if self.debug: print " Attributes: ", attrs + + + def parse_content_end_element(self, name): + if self.debug: print '* End element:', name + if name != self.parser_element: + print "Tag Mismatch: '%s' != '%s'" % (name, self.parser_element) + self.parser_element_list.pop() + + # If the element was text:p and we are in string mode + if (self.parser_element == 'text:p'): + if (self.parser_cell_string_pending): + self.parser_cell_string_pending = False + + # Take care of repeated cells + if (self.parser_element == 'table:table-cell'): + self.parser_sheet_column += self.parser_cell_repeats + + # Readjust parser_element_list and parser_element + if (self.parser_element_list): + self.parser_element = self.parser_element_list[-1] + else: + self.parser_element = "" + + def parse_content_char_data(self, data): + if self.debug: print " Character data: ", repr(data) + + if (self.parser_element == 'text:p' or self.parser_element == 'text:span'): + if (self.parser_cell_string_pending): + # Set the string and leave string pending mode + # This does feel a little kludgy, but it does the job + self.parser_cell_string_line = "%s%s" % (self.parser_cell_string_line, data) + + # I should do this once per cell repeat above 0 + for i in range(0, self.parser_cell_repeats+1): + self.set_cell_value(self.parser_sheet_column+i, self.parser_sheet_row, + 'string', self.parser_cell_string_line) + + + def content_parse(self, data): + "Parse Content Data from a content.xml file" + + # Debugging statements + if self.debug: + # Sometimes it helps to see the document that was read from + print data + print "\n\n\n" + + # Create parser + parser = xml.parsers.expat.ParserCreate() + # Set up parser callback functions + parser.StartElementHandler = self.parse_content_start_element + parser.EndElementHandler = self.parse_content_end_element + parser.CharacterDataHandler = self.parse_content_char_data + + # Actually parse the data + parser.Parse(data, 1) + + def save(self, filename): + """Save .ods spreadsheet. + + The save function saves the current cells and settings into a document. + """ + if self.debug: print "Writing %s" % filename + self.savefile = zipfile.ZipFile(filename, "w") + if self.debug: print " meta.xml" + self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta()) + if self.debug: print " mimetype" + self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.spreadsheet") + if self.debug: print " Configurations2/accelerator/current.xml" + self._zip_insert(self.savefile, "Configurations2/accelerator/current.xml", "") + if self.debug: print " META-INF/manifest.xml" + self._zip_insert(self.savefile, "META-INF/manifest.xml", self._ods_manifest()) + if self.debug: print " content.xml" + self._zip_insert(self.savefile, "content.xml", self._ods_content()) + if self.debug: print " settings.xml" + self._zip_insert(self.savefile, "settings.xml", self._ods_settings()) + if self.debug: print " styles.xml" + self._zip_insert(self.savefile, "styles.xml", self._ods_styles()) + + # Add additional files if needed + for fileset in self.manifest_files: + (filename, filetype, newname) = fileset + # Read in the file + data = self._file_load(filename) + if self.debug: print " Inserting '%s' as '%s'" % (filename, newname) + self._zip_insert_binary(self.savefile, newname, data) + + def _file_load(self, filename): + "Load a file" + file = open(filename, "rb") + data = file.read() + file.close() + return data + + def _zip_insert_binary(self, file, filename, data): + "Insert a binary file into the zip archive" + now = time.localtime(time.time())[:6] + info = zipfile.ZipInfo(filename) + info.date_time = now + info.compress_type = zipfile.ZIP_DEFLATED + file.writestr(info, data) + + + def _zip_insert(self, file, filename, data): + "Insert a file into the zip archive" + + # zip seems to struggle with non-ascii characters + data = data.encode('utf-8') + + now = time.localtime(time.time())[:6] + info = zipfile.ZipInfo(filename) + info.date_time = now + info.compress_type = zipfile.ZIP_DEFLATED + file.writestr(info, data) + + def _zip_read(self, file, filename): + "Get the data from a file in the zip archive by filename" + file = zipfile.ZipFile(file, "r") + data = file.read(filename) + # Need to close the file + file.close() + return data + + def _ods_content(self): + "Generate ods content.xml data" + + # This will list all of the sheets in the document + self.sheetdata = ['tag', 'office:spreadsheet'] + for sheet in self.sheets: + if self.debug: + sheet_name = sheet.get_name() + print " Creating Sheet '%s'" % sheet_name + sheet_list = sheet.get_lists() + self.sheetdata.append(sheet_list) + # Automatic Styles + self.automatic_styles = self.styles.get_automatic_styles() + + self.data = ['tag', 'office:document-content', + ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'], + ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'], + ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'], + ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'], + ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'], + ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'], + ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'], + ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'], + ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'], + ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'], + ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'], + ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'], + ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'], + ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'], + ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'], + ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'], + ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'], + ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'], + ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'], + ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'], + ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'], + ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'], + ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'], + ['element', 'office:version', '1.0'], + ['tagline', 'office:scripts'], + ['tag', 'office:font-face-decls', + ['tagline', 'style:font-face', + ['element', 'style:name', 'DejaVu Sans'], + ['element', 'svg:font-family', ''DejaVu Sans''], + ['element', 'style:font-pitch', 'variable']], + ['tagline', 'style:font-face', + ['element', 'style:name', 'Nimbus Sans L'], + ['element', 'svg:font-family', ''Nimbus Sans L''], + ['element', 'style:font-family-generic', 'swiss'], + ['element', 'style:font-pitch', 'variable']]], + + # Automatic Styles + self.automatic_styles, + + ['tag', 'office:body', + self.sheetdata]] # Sheets are generated from the CalcSheet class + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + + def _ods_manifest(self): + "Generate ods manifest.xml data" + self.data = ['tag', 'manifest:manifest', + ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet'], + ['element', 'manifest:full-path', '/']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'application/vnd.sun.xml.ui.configuration'], + ['element', 'manifest:full-path', 'Configurations2/']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', ''], + ['element', 'manifest:full-path', 'Configurations2/accelerator/']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', ''], + ['element', 'manifest:full-path', 'Configurations2/accelerator/current.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'content.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'styles.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'meta.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'settings.xml']]] + + # Add additional files to manifest list + for fileset in self.manifest_files: + (filename, filetype, newname) = fileset + addfile = ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', filetype], + ['element', 'manifest:full-path', newname]] + self.data.append(addfile) + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + + + def _ods_settings(self): + "Generate ods settings.xml data" + self.data = ['tag', 'office:document-settings', + ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'], + ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'], + ['element', 'xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0'], + ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'], + ['element', 'office:version', '1.0'], + ['tag', 'office:settings', + ['tag', 'config:config-item-set', + ['element', 'config:name', 'ooo:view-settings'], + ['tag', 'config:config-item', + ['element', 'config:name', 'VisibleAreaTop'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'VisibleAreaLeft'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'VisibleAreaWidth'], + ['element', 'config:type', 'int'], + ['data', '6774']], + ['tag', 'config:config-item', + ['element', 'config:name', 'VisibleAreaHeight'], + ['element', 'config:type', 'int'], + ['data', '2389']], + ['tag', 'config:config-item-map-indexed', + ['element', 'config:name', 'Views'], + ['tag', 'config:config-item-map-entry', + ['tag', 'config:config-item', + ['element', 'config:name', 'ViewId'], + ['element', 'config:type', 'string'], + ['data', 'View1']], + ['tag', 'config:config-item-map-named', + ['element', 'config:name', 'Tables'], + ['tag', 'config:config-item-map-entry', + ['element', 'config:name', 'Sheet1'], + ['tag', 'config:config-item', + ['element', 'config:name', 'CursorPositionX'], # Cursor Position A + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'CursorPositionY'], # Cursor Position 1 + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HorizontalSplitMode'], + ['element', 'config:type', 'short'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'VerticalSplitMode'], + ['element', 'config:type', 'short'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HorizontalSplitPosition'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'VerticalSplitPosition'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ActiveSplitRange'], + ['element', 'config:type', 'short'], + ['data', '2']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PositionLeft'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PositionRight'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PositionTop'], + ['element', 'config:type', 'int'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PositionBottom'], + ['element', 'config:type', 'int'], + ['data', '0']]]], + ['tag', 'config:config-item', + ['element', 'config:name', 'ActiveTable'], + ['element', 'config:type', 'string'], + ['data', 'Sheet1']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HorizontalScrollbarWidth'], + ['element', 'config:type', 'int'], + ['data', '270']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ZoomType'], + ['element', 'config:type', 'short'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ZoomValue'], + ['element', 'config:type', 'int'], + ['data', '100']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PageViewZoomValue'], + ['element', 'config:type', 'int'], + ['data', '60']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowPageBreakPreview'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowZeroValues'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowNotes'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowGrid'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'GridColor'], + ['element', 'config:type', 'long'], + ['data', '12632256']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowPageBreaks'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HasColumnRowHeaders'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HasSheetTabs'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsOutlineSymbolsSet'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsSnapToRaster'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterIsVisible'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterResolutionX'], + ['element', 'config:type', 'int'], + ['data', '1270']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterResolutionY'], + ['element', 'config:type', 'int'], + ['data', '1270']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterSubdivisionX'], + ['element', 'config:type', 'int'], + ['data', '1']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterSubdivisionY'], + ['element', 'config:type', 'int'], + ['data', '1']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsRasterAxisSynchronized'], + ['element', 'config:type', 'boolean'], + ['data', 'true']]]]], + ['tag', 'config:config-item-set', + ['element', 'config:name', 'ooo:configuration-settings'], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowZeroValues'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowNotes'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowGrid'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'GridColor'], + ['element', 'config:type', 'long'], + ['data', '12632256']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ShowPageBreaks'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'LinkUpdateMode'], + ['element', 'config:type', 'short'], + ['data', '3']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HasColumnRowHeaders'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'HasSheetTabs'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsOutlineSymbolsSet'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsSnapToRaster'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterIsVisible'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterResolutionX'], + ['element', 'config:type', 'int'], + ['data', '1270']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterResolutionY'], + ['element', 'config:type', 'int'], + ['data', '1270']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterSubdivisionX'], + ['element', 'config:type', 'int'], + ['data', '1']], + ['tag', 'config:config-item', + ['element', 'config:name', 'RasterSubdivisionY'], + ['element', 'config:type', 'int'], + ['data', '1']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsRasterAxisSynchronized'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'AutoCalculate'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PrinterName'], + ['element', 'config:type', 'string'], + ['data', 'Generic Printer']], + ['tag', 'config:config-item', + ['element', 'config:name', 'PrinterSetup'], + ['element', 'config:type', 'base64Binary'], + ['data', 'YgH+/0dlbmVyaWMgUHJpbnRlcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU0dFTlBSVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAMAqAAAAAAA//8FAFZUAAAkbQAASm9iRGF0YSAxCnByaW50ZXI9R2VuZXJpYyBQcmludGVyCm9yaWVudGF0aW9uPVBvcnRyYWl0CmNvcGllcz0xCnNjYWxlPTEwMAptYXJnaW5kYWp1c3RtZW50PTAsMCwwLDAKY29sb3JkZXB0aD0yNApwc2xldmVsPTAKY29sb3JkZXZpY2U9MApQUERDb250ZXhEYXRhClBhZ2VTaXplOkxldHRlcgAA']], + ['tag', 'config:config-item', + ['element', 'config:name', 'ApplyUserData'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'CharacterCompressionType'], + ['element', 'config:type', 'short'], + ['data', '0']], + ['tag', 'config:config-item', + ['element', 'config:name', 'IsKernAsianPunctuation'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'SaveVersionOnClose'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'UpdateFromTemplate'], + ['element', 'config:type', 'boolean'], + ['data', 'false']], + ['tag', 'config:config-item', + ['element', 'config:name', 'AllowPrintJobCancel'], + ['element', 'config:type', 'boolean'], + ['data', 'true']], + ['tag', 'config:config-item', + ['element', 'config:name', 'LoadReadonly'], + ['element', 'config:type', 'boolean'], + ['data', 'false']]]]] + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + + + def _ods_styles(self): + "Generate ods styles.xml data" + self.data = ['tag', 'office:document-styles', + ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'], + ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'], + ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'], + ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'], + ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'], + ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'], + ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'], + ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'], + ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'], + ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'], + ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'], + ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'], + ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'], + ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'], + ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'], + ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'], + ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'], + ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'], + ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'], + ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'], + ['element', 'office:version', '1.0'], + ['tag', 'office:font-face-decls', + ['tagline', 'style:font-face', + ['element', 'style:name', 'DejaVu Sans'], + ['element', 'svg:font-family', ''DejaVu Sans''], + ['element', 'style:font-pitch', 'variable']], + ['tagline', 'style:font-face', + ['element', 'style:name', 'Nimbus Sans L'], + ['element', 'svg:font-family', ''Nimbus Sans L''], + ['element', 'style:font-family-generic', 'swiss'], + ['element', 'style:font-pitch', 'variable']]], + ['tag', 'office:styles', + ['tag', 'style:default-style', + ['element', 'style:family', 'table-cell'], + ['tagline', 'style:table-cell-properties', + ['element', 'style:decimal-places', '2']], + ['tagline', 'style:paragraph-properties', + ['element', 'style:tab-stop-distance', '0.5in']], + ['tagline', 'style:text-properties', + ['element', 'style:font-name', 'Nimbus Sans L'], + ['element', 'fo:language', 'en'], + ['element', 'fo:country', 'US'], + ['element', 'style:font-name-asian', 'DejaVu Sans'], + ['element', 'style:language-asian', 'none'], + ['element', 'style:country-asian', 'none'], + ['element', 'style:font-name-complex', 'DejaVu Sans'], + ['element', 'style:language-complex', 'none'], + ['element', 'style:country-complex', 'none']]], + ['tag', 'number:number-style', + ['element', 'style:name', 'N0'], + ['tagline', 'number:number', + ['element', 'number:min-integer-digits', '1']]], + ['tag', 'number:currency-style', + ['element', 'style:name', 'N104P0'], + ['element', 'style:volatile', 'true'], + ['tag', 'number:currency-symbol', + ['element', 'number:language', 'en'], + ['element', 'number:country', 'US'], + ['data', '$']], + ['tagline', 'number:number', + ['element', 'number:decimal-places', '2'], + ['element', 'number:min-integer-digits', '1'], + ['element', 'number:grouping', 'true']]], + ['tag', 'number:currency-style', + ['element', 'style:name', 'N104'], + ['tagline', 'style:text-properties', + ['element', 'fo:color', '#ff0000']], + ['tag', 'number:text', + ['data', '-']], + ['tag', 'number:currency-symbol', + ['element', 'number:language', 'en'], + ['element', 'number:country', 'US'], + ['data', '$']], + ['tagline', 'number:number', + ['element', 'number:decimal-places', '2'], + ['element', 'number:min-integer-digits', '1'], + ['element', 'number:grouping', 'true']], + ['tagline', 'style:map', + ['element', 'style:condition', 'value()>=0'], + ['element', 'style:apply-style-name', 'N104P0']]], + ['tagline', 'style:style', + ['element', 'style:name', 'Default'], + ['element', 'style:family', 'table-cell']], + ['tag', 'style:style', + ['element', 'style:name', 'Result'], + ['element', 'style:family', 'table-cell'], + ['element', 'style:parent-style-name', 'Default'], + ['tagline', 'style:text-properties', + ['element', 'fo:font-style', 'italic'], + ['element', 'style:text-underline-style', 'solid'], + ['element', 'style:text-underline-width', 'auto'], + ['element', 'style:text-underline-color', 'font-color'], + ['element', 'fo:font-weight', 'bold']]], + ['tagline', 'style:style', + ['element', 'style:name', 'Result2'], + ['element', 'style:family', 'table-cell'], + ['element', 'style:parent-style-name', 'Result'], + ['element', 'style:data-style-name', 'N104']], + ['tag', 'style:style', + ['element', 'style:name', 'Heading'], + ['element', 'style:family', 'table-cell'], + ['element', 'style:parent-style-name', 'Default'], + ['tagline', 'style:table-cell-properties', + ['element', 'style:text-align-source', 'fix'], + ['element', 'style:repeat-content', 'false']], + ['tagline', 'style:paragraph-properties', + ['element', 'fo:text-align', 'center']], + ['tagline', 'style:text-properties', + ['element', 'fo:font-size', '16pt'], + ['element', 'fo:font-style', 'italic'], + ['element', 'fo:font-weight', 'bold']]], + ['tag', 'style:style', + ['element', 'style:name', 'Heading1'], + ['element', 'style:family', 'table-cell'], + ['element', 'style:parent-style-name', 'Heading'], + ['tagline', 'style:table-cell-properties', + ['element', 'style:rotation-angle', '90']]]], + ['tag', 'office:automatic-styles', + ['tag', 'style:page-layout', + ['element', 'style:name', 'pm1'], + ['tagline', 'style:page-layout-properties', + ['element', 'style:writing-mode', 'lr-tb']], + ['tag', 'style:header-style', + ['tagline', 'style:header-footer-properties', + ['element', 'fo:min-height', '0.2957in'], + ['element', 'fo:margin-left', '0in'], + ['element', 'fo:margin-right', '0in'], + ['element', 'fo:margin-bottom', '0.0984in']]], + ['tag', 'style:footer-style', + ['tagline', 'style:header-footer-properties', + ['element', 'fo:min-height', '0.2957in'], + ['element', 'fo:margin-left', '0in'], + ['element', 'fo:margin-right', '0in'], + ['element', 'fo:margin-top', '0.0984in']]]], + ['tag', 'style:page-layout', + ['element', 'style:name', 'pm2'], + ['tagline', 'style:page-layout-properties', + ['element', 'style:writing-mode', 'lr-tb']], + ['tag', 'style:header-style', + ['tag', 'style:header-footer-properties', + ['element', 'fo:min-height', '0.2957in'], + ['element', 'fo:margin-left', '0in'], + ['element', 'fo:margin-right', '0in'], + ['element', 'fo:margin-bottom', '0.0984in'], + ['element', 'fo:border', '0.0346in solid #000000'], + ['element', 'fo:padding', '0.0071in'], + ['element', 'fo:background-color', '#c0c0c0'], + ['tagline', 'style:background-image']]], + ['tag', 'style:footer-style', + ['tag', 'style:header-footer-properties', + ['element', 'fo:min-height', '0.2957in'], + ['element', 'fo:margin-left', '0in'], + ['element', 'fo:margin-right', '0in'], + ['element', 'fo:margin-top', '0.0984in'], + ['element', 'fo:border', '0.0346in solid #000000'], + ['element', 'fo:padding', '0.0071in'], + ['element', 'fo:background-color', '#c0c0c0'], + ['tagline', 'style:background-image']]]]], + ['tag', 'office:master-styles', + ['tag', 'style:master-page', + ['element', 'style:name', 'Default'], + ['element', 'style:page-layout-name', 'pm1'], + ['tag', 'style:header', + ['tag', 'text:p', + ['data', '<text:sheet-name>???</text:sheet-name>']]], + ['tagline', 'style:header-left', + ['element', 'style:display', 'false']], + ['tag', 'style:footer', + ['tag', 'text:p', + ['data', 'Page <text:page-number>1</text:page-number>']]], + ['tagline', 'style:footer-left', + ['element', 'style:display', 'false']]], + ['tag', 'style:master-page', + ['element', 'style:name', 'Report'], + ['element', 'style:page-layout-name', 'pm2'], + ['tag', 'style:header', + ['tag', 'style:region-left', + ['tag', 'text:p', + ['data', '<text:sheet-name>???</text:sheet-name> (<text:title>???</text:title>)']]], + ['tag', 'style:region-right', + ['tag', 'text:p', + ['data', '<text:date style:data-style-name="N2" text:date-value="2006-09-29">09/29/2006</text:date>, <text:time>13:02:56</text:time>']]]], + ['tagline', 'style:header-left', + ['element', 'style:display', 'false']], + ['tag', 'style:footer', + ['tag', 'text:p', + ['data', 'Page <text:page-number>1</text:page-number> / <text:page-count>99</text:page-count>']]], + ['tagline', 'style:footer-left', + ['element', 'style:display', 'false']]]]] + + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + +class Writer: + "Writer Class - Used to create OpenDocument Format Writer Documents." + def __init__(self): + "Initialize ooolib Writer instance" + # Default to no debugging + self.debug = False + self.meta = Meta('odt') + + def set_meta(self, metaname, value): + "Set meta data in your document." + self.meta.set_meta(metaname, value) + + def save(self, filename): + """Save .odt document + + The save function saves the current .odt document. + """ + if self.debug: print "Writing %s" % filename + self.savefile = zipfile.ZipFile(filename, "w") + if self.debug: print " meta.xml" + self._zip_insert(self.savefile, "meta.xml", self.meta.get_meta()) + if self.debug: print " mimetype" + self._zip_insert(self.savefile, "mimetype", "application/vnd.oasis.opendocument.text") + if self.debug: print " META-INF/manifest.xml" + self._zip_insert(self.savefile, "META-INF/manifest.xml", self._odt_manifest()) + if self.debug: print " content.xml" + self._zip_insert(self.savefile, "content.xml", self._odt_content()) + if self.debug: print " settings.xml" + # self._zip_insert(self.savefile, "settings.xml", self._odt_settings()) + if self.debug: print " styles.xml" + # self._zip_insert(self.savefile, "styles.xml", self._odt_styles()) + + # We need to close the file now that we are done creating it. + self.savefile.close() + + def _zip_insert(self, file, filename, data): + now = time.localtime(time.time())[:6] + info = zipfile.ZipInfo(filename) + info.date_time = now + info.compress_type = zipfile.ZIP_DEFLATED + file.writestr(info, data) + + def _odt_manifest(self): + "Generate odt manifest.xml data" + + self.data = ['tag', 'manifest:manifest', + ['element', 'xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'application/vnd.oasis.opendocument.text'], + ['element', 'manifest:full-path', '/']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'content.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'styles.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'meta.xml']], + ['tagline', 'manifest:file-entry', + ['element', 'manifest:media-type', 'text/xml'], + ['element', 'manifest:full-path', 'settings.xml']]] + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.lines.insert(1, '<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">') + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + + def _odt_content(self): + "Generate odt content.xml data" + + self.data = ['tag', 'office:document-content', + ['element', 'xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'], + ['element', 'xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'], + ['element', 'xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'], + ['element', 'xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'], + ['element', 'xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'], + ['element', 'xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'], + ['element', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'], + ['element', 'xmlns:dc', 'http://purl.org/dc/elements/1.1/'], + ['element', 'xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'], + ['element', 'xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'], + ['element', 'xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'], + ['element', 'xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'], + ['element', 'xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'], + ['element', 'xmlns:math', 'http://www.w3.org/1998/Math/MathML'], + ['element', 'xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'], + ['element', 'xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'], + ['element', 'xmlns:ooo', 'http://openoffice.org/2004/office'], + ['element', 'xmlns:ooow', 'http://openoffice.org/2004/writer'], + ['element', 'xmlns:oooc', 'http://openoffice.org/2004/calc'], + ['element', 'xmlns:dom', 'http://www.w3.org/2001/xml-events'], + ['element', 'xmlns:xforms', 'http://www.w3.org/2002/xforms'], + ['element', 'xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'], + ['element', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'], + ['element', 'office:version', '1.0'], + ['tagline', 'office:scripts'], + ['tag', 'office:font-face-decls', + ['tagline', 'style:font-face', + ['element', 'style:name', 'DejaVu Sans'], + ['element', 'svg:font-family', ''DejaVu Sans''], + ['element', 'style:font-pitch', 'variable']], + ['tagline', 'style:font-face', + ['element', 'style:name', 'Nimbus Roman No9 L'], + ['element', 'svg:font-family', ''Nimbus Roman No9 L''], + ['element', 'style:font-family-generic', 'roman'], + ['element', 'style:font-pitch', 'variable']], + ['tagline', 'style:font-face', + ['element', 'style:name', 'Nimbus Sans L'], + ['element', 'svg:font-family', ''Nimbus Sans L''], + ['element', 'style:font-family-generic', 'swiss'], + ['element', 'style:font-pitch', 'variable']]], + ['tagline', 'office:automatic-styles'], + ['tag', 'office:body', + ['tag', 'office:text', + ['tagline', 'office:forms', + ['element', 'form:automatic-focus', 'false'], + ['element', 'form:apply-design-mode', 'false']], + ['tag', 'text:sequence-decls', + ['tagline', 'text:sequence-decl', + ['element', 'text:display-outline-level', '0'], + ['element', 'text:name', 'Illustration']], + ['tagline', 'text:sequence-decl', + ['element', 'text:display-outline-level', '0'], + ['element', 'text:name', 'Table']], + ['tagline', 'text:sequence-decl', + ['element', 'text:display-outline-level', '0'], + ['element', 'text:name', 'Text']], + ['tagline', 'text:sequence-decl', + ['element', 'text:display-outline-level', '0'], + ['element', 'text:name', 'Drawing']]], + ['tagline', 'text:p', + ['element', 'text:style-name', 'Standard']]]]] + + # Generate content.xml XML data + xml = XML() + self.lines = xml.convert(self.data) + self.filedata = '\n'.join(self.lines) + # Return generated data + return self.filedata + + diff --git a/contrib/non-profit-audit-reports/readcsv.py b/contrib/non-profit-audit-reports/readcsv.py new file mode 100755 index 00000000..67fc5663 --- /dev/null +++ b/contrib/non-profit-audit-reports/readcsv.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +# readcsv.py +# CSV reading technical study +# +# Copyright (c) 2012 Tom Marble +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +import csv + +dialects = csv.list_dialects() +for dialect in dialects: + print 'dialect %s' % str(dialect) + +csvfile = open('tests/general-ledger.csv', 'rb') +reader = csv.reader(csvfile, delimiter=',', quotechar='"') +for row in reader: + print row diff --git a/contrib/non-profit-audit-reports/summary-reports.plx b/contrib/non-profit-audit-reports/summary-reports.plx new file mode 100755 index 00000000..5caef4f0 --- /dev/null +++ b/contrib/non-profit-audit-reports/summary-reports.plx @@ -0,0 +1,465 @@ +#!/usr/bin/perl +# fund-report.plx -*- Perl -*- +# +# Script to generate end-of-year summary reports. +# +# Copyright (C) 2011, 2012, 2013, Bradley M. Kuhn +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use Math::BigFloat; +use Date::Manip; + +my $VERBOSE = 0; +my $DEBUG = 0; + +my $LEDGER_BIN = "/usr/local/bin/ledger"; + +my $ACCT_WIDTH = 70; + +sub Commify ($) { + my $text = reverse $_[0]; + $text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; + return scalar reverse $text; +} + +sub preferredAccountSorting ($$) { + if ($_[0] =~ /^Assets/ and $_[1] !~ /^Assets/) { + return -1; + } elsif ($_[1] =~ /^Assets/ and $_[0] !~ /^Assets/) { + return 1; + } elsif ($_[0] =~ /^Liabilities/ and $_[1] !~ /^(Assets|Liabilities)/) { + return -1; + } elsif ($_[1] =~ /^Liabilities/ and $_[0] !~ /^(Assets|Liabilities)/) { + return 1; + } elsif ($_[0] =~ /^(Accrued:[^:]+Receivable)/ and $_[1] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { + return -1; + } elsif ($_[1] =~ /^(Accrued:[^:]+Receivable)/ and $_[0] !~ /^(Assets|Liabilities|Accrued:[^:]+Receivable)/) { + return 1; + } elsif ($_[0] =~ /^(Accrued)/ and $_[1] !~ /^(Assets|Liabilities|Accrued)/) { + return -1; + } elsif ($_[1] =~ /^(Accrued)/ and $_[0] !~ /^(Assets|Liabilities|Accrued)/) { + return 1; + } elsif ($_[0] =~ /^(Unearned Income)/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return -1; + } elsif ($_[1] =~ /^(Unearned Income)/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income)/) { + return 1; + } elsif ($_[0] =~ /^Income/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { + return -1; + } elsif ($_[1] =~ /^Income/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Unearned Income|Income)/) { + return 1; + } elsif ($_[0] =~ /^Expense/ and $_[1] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { + return -1; + } elsif ($_[1] =~ /^Expense/ and $_[0] !~ /^(Assets|Liabilities|Accrued|Income|Unearned Income|Expense)/) { + return 1; + } else { + return $_[0] cmp $_[1]; + } +} + +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); +my $ONE_PENNY = Math::BigFloat->new("0.01"); +my $TWO_CENTS = Math::BigFloat->new("0.02"); + +if (@ARGV < 2) { + print STDERR "usage: $0 <START_DATE> <END_DATE> <LEDGER_OPTIONS>\n"; + exit 1; +} +my($startDate, $endDate, @mainLedgerOptions) = @ARGV; + +my $err; +my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), + "%B %e, %Y"); +die "Date calculation error on $endDate" if ($err); +my $formattedStartDate = UnixDate(ParseDate($startDate), "%B %e, %Y"); +die "Date calculation error on $startDate" if ($err); + +my %reportFields = + ('Cash' => { args => [ '-e', $endDate, 'bal', '/^Assets/' ] }, + 'Accounts Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Accounts Receivable/' ]}, + 'Loans Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:Loans Receivable/' ]}, + 'Accounts Payable' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Accounts Payable/' ]}, + 'Accrued Expenses' => {args => [ '-e', $endDate, 'bal', '/^Accrued.*Expenses/' ]}, + 'Liabilities, Credit Cards' => {args => [ '-e', $endDate, 'bal', '/^Liabilities:Credit Card/' ]}, + 'Liabilities, Other' => {args => [ '-e', $endDate, 'bal', '/^Liabilities/', + 'and', 'not', '/^Liabilities:Credit Card/']}, + 'Unearned Income, Conference Registration' => {args => [ '-e', $endDate, 'bal', + '/^Unearned Income.*Reg/' ]}, + 'Unearned Income, Other' => {args => [ '-e', $endDate, 'bal', '/^Unearned Income/', 'and', 'not', + '/^Unearned Income.*Reg/' ]}, + 'Unrestricted Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses):Conservancy/' ]}, + 'Temporarily Restricted Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses)/', + 'and', 'not', '/^(Unearned Income|(Income|Expenses):Conservancy)/' ]}, + 'Total Net Assets' => {args => [ '-e', $endDate, 'bal', '/^(Income|Expenses)/' ]}, + +); +foreach my $item (keys %reportFields) { + my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, + '-V', '-X', '$', '-S', 'T', '-s', '-d', 'T', @{$reportFields{$item}{args}}); + open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + + my $foundBalance; + my $seenTotalLine = 0; + + print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + print STDERR " Output of @fullCommand\n" if $DEBUG; + + while (my $line = <FILE>) { + print STDERR $line if ($DEBUG); + + $seenTotalLine = 1 if $line =~ /^\s*\-+\s*/; # Skip lines until the total line + $foundBalance = $1 + if (not $seenTotalLine and $line =~ /^\s*[^0-9\-]+\s*([\-\d,\.]+)\s+/); + + if ($line =~ /^\s*\$\s*([\-\d,\.]+)\s*$/) { + $foundBalance = $1; + last; + } + } + close FILE; + die "problem running ledger command: @fullCommand: $!" unless ($? == 0); + if (not defined $foundBalance) { + $foundBalance = $ZERO; + } else { + $foundBalance =~ s/,//g; + $foundBalance = Math::BigFloat->new($foundBalance); + } + $foundBalance = $ZERO if not defined $foundBalance; + $reportFields{$item}{total} = abs($foundBalance); + print STDERR "$item: $reportFields{$item}{total}\n" if $VERBOSE; +} + +open(BALANCE_SHEET, ">", "balance-sheet.csv") + or die "unable to open balance-sheet.csv for writing: $!"; + +print BALANCE_SHEET "\"BALANCE SHEET\"\n", + "\"Ending\",\"", $formattedEndDate, "\"\n", + "\n\n\"ASSETS\"\n\n"; + +my $formatStr = "\"\",\"%-42s\",\"\$%13s\"\n"; +my $formatStrTotal = "\"\",\"%-45s\",\"\$%13s\"\n"; +my $tot = $ZERO; +foreach my $item ('Cash', 'Accounts Receivable', 'Loans Receivable') { + next if $reportFields{$item}{total} == $ZERO; + print BALANCE_SHEET sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + $tot += $reportFields{$item}{total}; +} +print BALANCE_SHEET "\n", sprintf($formatStrTotal, "TOTAL ASSETS", Commify($tot)), "\n\nLIABILITIES\n\n"; + +my $totLiabilities = $ZERO; +foreach my $item ('Accounts Payable', 'Accrued Expenses', + 'Liabilities, Credit Cards', 'Liabilities, Other', + 'Unearned Income, Conference Registration', 'Unearned Income, Other') { + next if $reportFields{$item}{total} == $ZERO; + print BALANCE_SHEET sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + $totLiabilities += $reportFields{$item}{total}; +} +print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL LIABILTIES", Commify($totLiabilities)), + "\n\nNET ASSETS\n\n"; + +my $totNetAssets = $ZERO; +foreach my $item ('Unrestricted Net Assets', 'Temporarily Restricted Net Assets') { + next if $reportFields{$item}{total} == $ZERO; + print BALANCE_SHEET sprintf($formatStr, "$item:", Commify($reportFields{$item}{total})); + $totNetAssets += $reportFields{$item}{total}; +} +print BALANCE_SHEET "\n", sprintf($formatStr, "TOTAL NET ASSETS", Commify($totNetAssets)), "\n\n", + sprintf($formatStrTotal, "TOTAL LIABILITIES AND NET ASSETS", + Commify($totNetAssets + $totLiabilities)); + +close BALANCE_SHEET; +print STDERR "\n"; +die "unable to write to balance-sheet.csv: $!" unless ($? == 0); + +die "Cash+accounts receivable total does not equal net assets and liabilities total" + if (abs( ($reportFields{'Cash'}{total} + $reportFields{'Accounts Receivable'}{total} + + $reportFields{'Loans Receivable'}{total})) - + abs($reportFields{'Accounts Payable'}{total} + + $reportFields{'Accrued Expenses'}{total} + + $reportFields{'Unearned Income, Conference Registration'}{total} + + $reportFields{'Unearned Income, Other'}{total} + + $reportFields{'Liabilities, Credit Cards'}{total} + + $reportFields{'Liabilities, Other'}{total} + + $reportFields{'Total Net Assets'}{total}) > $ONE_PENNY); + +die "Total net assets doesn't equal sum of restricted and unrestricted ones!" + if (abs($reportFields{'Total Net Assets'}{total}) - + abs($reportFields{'Unrestricted Net Assets'}{total} + + $reportFields{'Temporarily Restricted Net Assets'}{total}) > $ONE_PENNY); + + +my %incomeGroups = ('INTEREST INCOME' => { args => ['/^Income.*Interest/' ] }, + 'DONATIONS' => { args => [ '/^Income.*Donation/' ] }, + 'BOOK ROYALTIES & AFFILIATE PROGRAMS' => + { args => [ '/^Income.*(Royalt|Affilate)/' ] }, + 'CONFERENCES, REGISTRATION' => {args => [ '/^Income.*Reg/' ] }, + 'CONFERENCES, RELATED BUSINESS INCOME' => { args => [ '/^Income.*(Conferences?:.*Sponsor|Booth|RBI)/'] }, + 'LICENSE ENFORCEMENT' => { args => [ '/^Income.*Enforce/' ]}, + 'TRADEMARKS' => {args => [ '/^Income.*Trademark/' ]}, + 'ADVERSITING' => {args => [ '/^Income.*Advertising/' ]}); + +my @otherArgs; +foreach my $type (keys %incomeGroups) { + @otherArgs = ("/^Income/") if @otherArgs == 0; + push(@otherArgs, 'and', 'not', @{$incomeGroups{$type}{args}}); +} +$incomeGroups{"OTHER"}{args} = \@otherArgs; +$incomeGroups{"TOTAL"}{args} = ['/^Income/']; + +open(INCOME, ">", "income.csv") or die "unable to open income.csv for writing: $!"; + +foreach my $type (keys %incomeGroups) { + my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, + '-F', '%-.80A %22.108t\n', '-s', + 'reg', @{$incomeGroups{$type}{args}}); + + open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + + print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + + $incomeGroups{$type}{total} = $ZERO; + $incomeGroups{$type}{output} = ""; + + foreach my $line (<FILE>) { + die "Unable to parse output line from second funds command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\<Adjustment\>/ and (abs($amount) <= $TWO_CENTS); + die "Weird account found, $account with amount of $amount in income command\n" + unless $account =~ /^\s*Income:/; + + $incomeGroups{$type}{total} += $amount; + $incomeGroups{$type}{output} .= "\"$account\",\"\$$amount\"\n"; + } +} +print INCOME "\"INCOME\",", + "\"STARTING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n"; + + +my $overallTotal = $ZERO; + +$formatStrTotal = "\"%-90s\",\"\$%14s\"\n"; +foreach my $type ('DONATIONS', 'LICENSE ENFORCEMENT', + 'CONFERENCES, REGISTRATION', 'CONFERENCES, RELATED BUSINESS INCOME', + 'BOOK ROYALTIES & AFFILIATE PROGRAMS', 'ADVERSITING', + 'TRADEMARKS', 'INTEREST INCOME', 'OTHER') { + next if ($incomeGroups{$type}{output} =~ /^\s*$/ and $incomeGroups{$type}{total} == $ZERO); + print INCOME "\n\"$type\"\n", + $incomeGroups{$type}{output}, "\n", + sprintf($formatStrTotal, "TOTAL $type:", Commify($incomeGroups{$type}{total})); + $overallTotal += $incomeGroups{$type}{total}; +} +print INCOME "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal)); + +close INCOME; die "unable to write to income.csv: $!" unless ($? == 0); + +die "calculated total of $overallTotal does equal $incomeGroups{TOTAL}{total}" + if (abs($overallTotal) - abs($incomeGroups{TOTAL}{total}) > $ONE_PENNY); + +print STDERR "\n"; + +my %expenseGroups = ('BANKING FEES' => { regex => '^Expenses.*(Banking Fees|Currency Conversion)' }, + 'COMPUTING, HOSTING AND EQUIPMENT' => { regex => '^Expenses.*(Hosting|Computer Equipment)' }, + 'CONFERENCES' => { regex => '^Expenses.*(Conferences|Sprint)' }, + 'DEVELOPER MENTORING' => {regex => '^Expenses.*Mentor' }, + 'LICENSE ENFORCEMENT' => { regex => '^Expenses.*Enforce' }, + 'ACCOUNTING' => { regex => '^Expenses.*(Accounting|Annual Audit)' }, + 'PAYROLL' => { regex => '^Expenses.*Payroll' }, + 'OFFICE' => { regex => '^Expenses.*(Office|Phones)' }, + 'RENT' => { regex => '^Expenses.*Rent' }, + 'SOFTWARE DEVELOPMENT' => { regex => '^Expenses.*Development' }, + 'OTHER PROGRAM ACTIVITY' => {regex => '^Expenses.*Gould' }, + 'ADVOCACY AND PROMOTION' => {regex => '^Expenses.*(Slipstream|Advocacy Merchandise|Promotional)' }, + 'ADVERSITING' => {regex => '^Expenses.*Advertising' }); + +foreach my $type (keys %expenseGroups, 'TRAVEL') { + $expenseGroups{$type}{total} = $ZERO; + $expenseGroups{$type}{output} = ""; +} + +open(EXPENSE, ">", "expense.csv") or die "unable to open expense.csv for writing: $!"; + +my(@fullCommand) = ($LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, + '-F', '%-.80A %22.108t\n', '-s', + 'reg', '/^Expenses/'); + +open(FILE, "-|", @fullCommand) + or die "unable to run command ledger command: @fullCommand: $!"; + +print STDERR ($VERBOSE ? "Running: @fullCommand\n" : "."); + +my $firstTotal = $ZERO; +foreach my $line (<FILE>) { + die "Unable to parse output line from second funds command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\<Adjustment\>/ and (abs($amount) <= $TWO_CENTS); + die "Weird account found, $account, with amount of $amount in expenses command\n" + unless $account =~ /^\s*Expenses:/; + + my $outputLine = "\"$account\",\"\$$amount\"\n"; + my $taken = 0; + # Note: Prioritize to put things under conference expenses if they were for a conference. + foreach my $type ('CONFERENCES', keys %expenseGroups) { + last if $taken; + next if $type eq 'TRAVEL' or $type eq 'OTHER'; + next unless $line =~ /$expenseGroups{$type}{regex}/; + $taken = 1; + $expenseGroups{$type}{total} += $amount; + $expenseGroups{$type}{output} .= $outputLine; + } + if (not $taken) { + if ($account =~ /Travel/) { + $expenseGroups{'TRAVEL'}{total} += $amount; + $expenseGroups{'TRAVEL'}{output} .= $outputLine; + } else { + $expenseGroups{'OTHER'}{total} += $amount; + $expenseGroups{'OTHER'}{output} .= $outputLine; + } + } + $firstTotal += $amount; +} +print EXPENSE "\"EXPENSES\",", + "\"STARTING:\",\"$formattedStartDate\",\"ENDING:\",\"$formattedEndDate\"\n\n"; +$overallTotal = $ZERO; +$formatStrTotal = "\"%-90s\",\"\$%14s\"\n"; + +my %verifyAllGroups; +foreach my $key (keys %expenseGroups) { + $verifyAllGroups{$key} = 1; +} +foreach my $type ('PAYROLL', 'SOFTWARE DEVELOPMENT', 'LICENSE ENFORCEMENT', 'CONFERENCES', + 'DEVELOPER MENTORING', 'TRAVEL', 'BANKING FEES', 'ADVOCACY AND PROMOTION', + 'COMPUTING, HOSTING AND EQUIPMENT', 'ACCOUNTING', + 'OFFICE', 'RENT', 'ADVERSITING', 'OTHER PROGRAM ACTIVITY', 'OTHER') { + delete $verifyAllGroups{$type}; + + die "$type is not defined!" if not defined $expenseGroups{$type}; + next if ($expenseGroups{$type}{output} =~ /^\s*$/ and $expenseGroups{$type}{total} == $ZERO); + + print EXPENSE "\n\"$type\"\n", + $expenseGroups{$type}{output}, "\n", + sprintf($formatStrTotal, "TOTAL $type:", Commify($expenseGroups{$type}{total})); + $overallTotal += $expenseGroups{$type}{total}; +} + +print EXPENSE "\n\n\n", sprintf($formatStrTotal, "OVERALL TOTAL:", Commify($overallTotal)); + +close EXPENSE; die "unable to write to expense.csv: $!" unless ($? == 0); + +die "GROUPS NOT INCLUDED : ", join(keys(%verifyAllGroups), ", "), "\n" + unless (keys %verifyAllGroups == 0); + +die "calculated total of $overallTotal does *not* equal $firstTotal" + if (abs($overallTotal) - abs($firstTotal) > $ONE_PENNY); + +print STDERR "\n"; + +open(TRIAL, ">", "trial-balance.csv") or die "unable to open accrued.txt for writing: $!"; + +print TRIAL "\"TRIAL BALANCE REPORT\",\"ENDING: $formattedEndDate\"\n\n", + "\"ACCOUNT\",\"BALANCE AT $formattedStartDate\",\"CHANGE DURING PERIOD\",\"BALANCE AT $formattedEndDate\"\n\n"; + +my %commands = ( + 'totalEndFY' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-e', $endDate, '-F', '%-.80A %22.108t\n', '-s', + 'reg' ], + 'amountInYear' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-b', $startDate, '-e', $endDate, '-F', '%-.80A %22.108t\n', + '-s', 'reg' ], + 'totalBeginFY' => [ $LEDGER_BIN, @mainLedgerOptions, '-V', '-X', '$', + '-e', $startDate, '-F', '%-.80A %22.108t\n', + '-s', 'reg' ]); + +my %trialBalanceData; +my %fullAccountList; + +foreach my $id (keys %commands) { + my(@command) = @{$commands{$id}}; + + open(FILE, "-|", @command) + or die "unable to run command ledger command: @command: $!"; + + print STDERR ($VERBOSE ? "Running: @command\n" : "."); + + foreach my $line (<FILE>) { + die "Unable to parse output line from trial balance $id command: $line" + unless $line =~ /^\s*([^\$]+)\s+\$\s*([\-\d\.\,]+)/; + my($account, $amount) = ($1, $2); + $amount = ParseNumber($amount); + $account =~ s/\s+$//; + next if $account =~ /\<Adjustment\>/ and (abs($amount) <= $TWO_CENTS); + next if $account =~ /^Equity:/; # Stupid auto-account made by ledger. + $trialBalanceData{$id}{$account} = $amount; + $fullAccountList{$account} = $id; + } + close FILE; + die "unable to run trial balance ledger command, @command: $!" unless ($? == 0); +} + +my $curOn = 'Assets'; + +foreach my $account (sort preferredAccountSorting keys %fullAccountList) { + # Blank lines right + if ($account !~ /^$curOn/) { + print TRIAL "pagebreak\n"; + $curOn = $account; + if ($curOn =~ /(Accrued:[^:]+):.*$/) { + $curOn = $1; + } else { + $curOn =~ s/^([^:]+):.*$/$1/; + } + } + if ($account =~ /^Assets|Liabilities|Accrued|Unearned Income/) { + foreach my $id (qw/totalBeginFY totalEndFY amountInYear/) { + $trialBalanceData{$id}{$account} = $ZERO + unless defined $trialBalanceData{$id}{$account}; + } + print TRIAL "\"$account\",\"\$$trialBalanceData{totalBeginFY}{$account}\",", + "\"\$$trialBalanceData{amountInYear}{$account}\",\"\$$trialBalanceData{totalEndFY}{$account}\"\n" + unless $trialBalanceData{totalBeginFY}{$account} == $ZERO and + $trialBalanceData{amountInYear}{$account} == $ZERO and + $trialBalanceData{totalEndFY}{$account} == $ZERO; + } else { + print TRIAL "\"$account\",\"\",\"\$$trialBalanceData{amountInYear}{$account}\",\"\"\n" + if defined $trialBalanceData{amountInYear}{$account} and + $trialBalanceData{amountInYear}{$account} != $ZERO; + } +} +close TRIAL; +die "unable to write trial-balance.csv: $!" unless ($? == 0); + +############################################################################### +# +# Local variables: +# compile-command: "perl -c summary-reports.plx" +# End: diff --git a/contrib/non-profit-audit-reports/tests/Financial/BankStuff/bank-statement.pdf b/contrib/non-profit-audit-reports/tests/Financial/BankStuff/bank-statement.pdf Binary files differnew file mode 100644 index 00000000..27b40353 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Financial/BankStuff/bank-statement.pdf diff --git a/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.pdf b/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.pdf Binary files differnew file mode 100644 index 00000000..e2e06c98 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.pdf diff --git a/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.txt b/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.txt new file mode 100644 index 00000000..4d5fc907 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.txt @@ -0,0 +1,5 @@ +Invoice + +Date: May 10, 2011 + +Donation to the General Fund: $50.00 diff --git a/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf b/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf new file mode 100644 index 00000000..b6937670 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf @@ -0,0 +1,106 @@ +%PDF-1.4 +%쏢 +5 0 obj +<</Length 6 0 R/Filter /FlateDecode>> +stream +xn0E~1q#]4@;0x؆*tQ!d_Bp:;=F&='(X}]D\+cQٲ K^jyYHTTMq<q#v纹'nn~]c$33Ah{LC
%Ԃq{þVI!JAA*kʼn1]3{f&0"Lbwɿ"XP~v9BVxu`@Yz=t1o+Wʻk/S-/RWQB"5}m?^f|}8?M*2 o 8?Qsv_RGSdrendstream +endobj +6 0 obj +369 +endobj +4 0 obj +<</Type/Page/MediaBox [0 0 612 792] +/Rotate 0/Parent 3 0 R +/Resources<</ProcSet[/PDF /Text] +/ExtGState 11 0 R +/Font 12 0 R +>> +/Contents 5 0 R +>> +endobj +3 0 obj +<< /Type /Pages /Kids [ +4 0 R +] /Count 1 +>> +endobj +1 0 obj +<</Type /Catalog /Pages 3 0 R +/Metadata 13 0 R +>> +endobj +7 0 obj +<</Type/ExtGState +/OPM 1>>endobj +11 0 obj +<</R7 +7 0 R>> +endobj +12 0 obj +<</R9 +9 0 R/R8 +8 0 R/R10 +10 0 R>> +endobj +9 0 obj +<</BaseFont/Helvetica/Type/Font +/Subtype/Type1>> +endobj +8 0 obj +<</BaseFont/Courier/Type/Font +/Subtype/Type1>> +endobj +10 0 obj +<</BaseFont/Helvetica-Bold/Type/Font +/Subtype/Type1>> +endobj +13 0 obj +<</Type/Metadata +/Subtype/XML/Length 1393>>stream +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<?adobe-xap-filters esc="CRLF"?> +<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='XMP toolkit 2.9.1-13, framework 1.6'> +<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/'> +<rdf:Description rdf:about='1335afed-313b-11ed-0000-eb02a9a83ec4' xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 8.71'/> +<rdf:Description rdf:about='1335afed-313b-11ed-0000-eb02a9a83ec4' xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2012-09-07T15:02:10-04:00</xmp:ModifyDate> +<xmp:CreateDate>2012-09-07T15:02:10-04:00</xmp:CreateDate> +<xmp:CreatorTool>a2ps version 4.14</xmp:CreatorTool></rdf:Description> +<rdf:Description rdf:about='1335afed-313b-11ed-0000-eb02a9a83ec4' xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='1335afed-313b-11ed-0000-eb02a9a83ec4'/> +<rdf:Description rdf:about='1335afed-313b-11ed-0000-eb02a9a83ec4' xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>receipt</rdf:li></rdf:Alt></dc:title><dc:creator><rdf:Seq><rdf:li>Bradley M. Kuhn</rdf:li></rdf:Seq></dc:creator></rdf:Description> +</rdf:RDF> +</x:xmpmeta> + + +<?xpacket end='w'?> +endstream +endobj +2 0 obj +<</Producer(GPL Ghostscript 8.71) +/CreationDate(D:20120907150210-04'00') +/ModDate(D:20120907150210-04'00') +/Title(receipt) +/Author(Bradley M. Kuhn) +/Creator(a2ps version 4.14)>>endobj +xref +0 14 +0000000000 65535 f +0000000692 00000 n +0000002544 00000 n +0000000633 00000 n +0000000473 00000 n +0000000015 00000 n +0000000454 00000 n +0000000757 00000 n +0000000942 00000 n +0000000878 00000 n +0000001004 00000 n +0000000798 00000 n +0000000828 00000 n +0000001074 00000 n +trailer +<< /Size 14 /Root 1 0 R /Info 2 0 R +/ID [<346C5213A8B2262C0696706A70350365><346C5213A8B2262C0696706A70350365>] +>> +startxref +2736 +%%EOF diff --git a/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/april-invoice.pdf b/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/april-invoice.pdf Binary files differnew file mode 100644 index 00000000..7241909a --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/april-invoice.pdf diff --git a/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf b/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf Binary files differnew file mode 100644 index 00000000..8441f3e6 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf diff --git a/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.txt b/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.txt new file mode 100644 index 00000000..e2722c45 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/AprilHostingReceipt.txt @@ -0,0 +1,6 @@ +Baz Hosting Services, LLC + +Date: April 20, 2011 + +Charge: $250.00 + diff --git a/contrib/non-profit-audit-reports/tests/Projects/Foo/Invoices/Invoice20100101.pdf b/contrib/non-profit-audit-reports/tests/Projects/Foo/Invoices/Invoice20100101.pdf Binary files differnew file mode 100644 index 00000000..11f6286c --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Projects/Foo/Invoices/Invoice20100101.pdf diff --git a/contrib/non-profit-audit-reports/tests/Projects/Foo/earmark-record.txt b/contrib/non-profit-audit-reports/tests/Projects/Foo/earmark-record.txt new file mode 100644 index 00000000..c5ac98ac --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/Projects/Foo/earmark-record.txt @@ -0,0 +1 @@ +I, Another J. Donor, would like $400 to be earmarked for Foo! diff --git a/contrib/non-profit-audit-reports/tests/non-profit-test-data.ledger b/contrib/non-profit-audit-reports/tests/non-profit-test-data.ledger new file mode 100644 index 00000000..fb6134ff --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/non-profit-test-data.ledger @@ -0,0 +1,28 @@ + +2010/01/01 Kindly T. Donor + Income:Foo:Donation $-100.00 + ;Invoice: Projects/Foo/Invoices/Invoice20100101.pdf + Assets:Checking $100.00 + + +2011/03/15 Another J. Donor + Income:Foo:Donation $-400.00 + ;Approval: Projects/Foo/earmark-record.txt + Assets:Checking $400.00 + +2011/04/20 (1) Baz Hosting Services, LLC + Expenses:Foo:Hosting $250.00 + ;Receipt: Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf + Assets:Checking $-250.00 + +2011/05/10 Donation to General Fund + Income:Donation $-50.00 + ;Invoice: Financial/Invoices/Invoice20110510.pdf + Assets:Checking $50.00 + +2011/04/20 (2) Baz Hosting Services, LLC + Expenses:Blah:Hosting $250.00 + ;Receipt: Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf + ;Invoice: Projects/Blah/Expenses/hosting/april-invoice.pdf + Assets:Checking $-250.00 + ;Statement: Financial/BankStuff/bank-statement.pdf diff --git a/contrib/non-profit-audit-reports/tests/non-profit-test-data_MANIFEST b/contrib/non-profit-audit-reports/tests/non-profit-test-data_MANIFEST new file mode 100644 index 00000000..b8bfc107 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/non-profit-test-data_MANIFEST @@ -0,0 +1,10 @@ +chart-of-accounts.csv +general-ledger.txt +general-ledger.csv +Financial/BankStuff/bank-statement.pdf +Financial/Invoices/Invoice20110510.pdf +Projects/Foo/Invoices/Invoice20100101.pdf +Projects/Foo/earmark-record.txt +Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf +Projects/Blah/Expenses/hosting/april-invoice.pdf +Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf diff --git a/contrib/non-profit-audit-reports/tests/non-profit-test-data_chart-of-accounts.csv b/contrib/non-profit-audit-reports/tests/non-profit-test-data_chart-of-accounts.csv new file mode 100644 index 00000000..445bc412 --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/non-profit-test-data_chart-of-accounts.csv @@ -0,0 +1,6 @@ +"CHART OF ACCOUNTS","BEGINNING:","2012/03/01","ENDING:","2012/02/29" +"Assets:Checking" +"Income:Donation" +"Income:Foo:Donation" +"Expenses:Blah:Hosting" +"Expenses:Foo:Hosting" diff --git a/contrib/non-profit-audit-reports/tests/non-profit-test-data_general-ledger.ods b/contrib/non-profit-audit-reports/tests/non-profit-test-data_general-ledger.ods Binary files differnew file mode 100644 index 00000000..8eae706f --- /dev/null +++ b/contrib/non-profit-audit-reports/tests/non-profit-test-data_general-ledger.ods diff --git a/contrib/non-profit-audit-reports/unpaid-accruals-report.plx b/contrib/non-profit-audit-reports/unpaid-accruals-report.plx new file mode 100755 index 00000000..f481e02f --- /dev/null +++ b/contrib/non-profit-audit-reports/unpaid-accruals-report.plx @@ -0,0 +1,110 @@ +#!/usr/bin/perl +# unpaid-acccurals-report.plx -*- Perl -*- + +# This report is designed to create what our accounts call a "Schedule of +# accounts payable". and "Schedule of accounts receivable". + + + +# Copyright (C) 2013 Bradley M. Kuhn +# +# This program gives you software freedom; you can copy, modify, convey, +# and/or redistribute it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 of the +# License, 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 in a file called 'GPLv3'. If not, write to the: +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor +# Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use Math::BigFloat; +use Date::Manip; + +my $LEDGER_CMD = "/usr/local/bin/ledger"; + +my $ACCT_WIDTH = 70; + +sub ParseNumber($) { + $_[0] =~ s/,//g; + return Math::BigFloat->new($_[0]); +} +Math::BigFloat->precision(-2); +my $ZERO = Math::BigFloat->new("0.00"); +my $TWO_CENTS = Math::BigFloat->new("0.02"); + +if (@ARGV < 2) { + print STDERR "usage: $0 <START_DATE> <END_DATE> <LEDGER_OPTIONS>\n"; + exit 1; +} +my($startDate, $endDate, @mainLedgerOptions) = @ARGV; + +my $err; +my $formattedEndDate = UnixDate(DateCalc(ParseDate($endDate), ParseDateDelta("- 1 day"), \$err), + "%Y/%m/%d"); +die "Date calculation error on $endDate" if ($err); +my $formattedStartDate = UnixDate(ParseDate($startDate), "%Y/%m/%d"); +die "Date calculation error on $startDate" if ($err); + +my(@ledgerOptions) = (@mainLedgerOptions, + '-V', '-X', '$', '-e', $endDate, '-F', + '\"%(tag("Invoice"))\",\"%A\",\"%(date)\",\"%(payee)\",\"%22.108t\"\n', + '--limit', 'tag("Invoice") !~ /^\s*$/', 'reg'); + +my @possibleTypes = ('Accrued:Loans Receivable', 'Accrued:Accounts Payable', + 'Accrued:Accounts Receivable', 'Accrued:Expenses'); + +my %data; +foreach my $type (@possibleTypes) { + open(LEDGER_FUNDS, "-|", $LEDGER_CMD, @ledgerOptions, "/^$type/") + or die "Unable to run $LEDGER_CMD @ledgerOptions: $!"; + + while (my $line = <LEDGER_FUNDS>) { + next if $line =~ /"\<Adjustment\>"/; + die "Unable to parse output line $line from @ledgerOptions" + + unless $line =~ /^\s*"([^"]+)","([^"]+)","([^"]+)","([^"]+)","\s*\$\s*([\-\d\.\,]+)"\s*$/; + my($invoice, $account, $date, $payee, $amount) = ($1, $2, $3, $4, $5); + $amount = ParseNumber($amount); + + push(@{$data{$type}{$invoice}{entries}}, { account => $account, date => $date, payee => $payee, amount => $amount}); + $data{$type}{$invoice}{total} = $ZERO unless defined $data{$type}{$invoice}{total}; + $data{$type}{$invoice}{total} += $amount; + } + close LEDGER_FUNDS; + die "Failure on ledger command for $type: $!" unless ($? == 0); + +} +foreach my $type (keys %data) { + foreach my $invoice (keys %{$data{$type}}) { + delete $data{$type}{$invoice} if abs($data{$type}{$invoice}{total}) <= $TWO_CENTS; + } +} +foreach my $type (keys %data) { + delete $data{$type} if scalar(keys %{$data{$type}}) == 0; +} +foreach my $type (keys %data) { + print "\"SCHEDULE OF $type\"\n\"ENDING:\",\"$formattedEndDate\"\n\n", + '"DATE","PAYEE","ACCOUNT","AMOUNT","INVOICE"', "\n"; + foreach my $invoice (keys %{$data{$type}}) { + my $vals; + foreach my $vals (@{$data{$type}{$invoice}{entries}}) { + print "\"$vals->{date}\",\"$vals->{payee}\",\"$vals->{account}\",\"\$$vals->{amount}\",\"link:$invoice\"\n"; + } + } + print "pagebreak\n"; +} +############################################################################### +# +# Local variables: +# compile-command: "perl -c unpaid-accruals-report.plx" +# End: + diff --git a/contrib/raw/GenerateLatexExpeneseReport.pl b/contrib/raw/GenerateLatexExpeneseReport.pl new file mode 100755 index 00000000..670d9f21 --- /dev/null +++ b/contrib/raw/GenerateLatexExpeneseReport.pl @@ -0,0 +1,429 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Getopt::Long; # Options processing +use Smart::Comments -ENV, "###"; # Ignore my dividers, and use + # Smart_Comments=1 to activate +use Cwd; +use File::Basename; +use 5.10.0; +use POSIX qw(strftime); +use Date::Calc qw(Add_Delta_Days); + +use Template; +my $TT = Template->new( { POST_CHOMP => 1 } ); + +###################################################################### +# TODO +# +# DONE Meal summaries are broken for multi-week reports + +###################################################################### +# Options + +# Is this an internal report? +my $ExpenseReportCode = undef; +my $Internal = undef; +my $SuppressMeals = 0; +my $ViewAfter = 0; +my $ImageDir = "."; +my $Anonymize = 0; +my $Help = undef; + +GetOptions( 'c' => \$Internal, + 'm' => \$SuppressMeals, + 'v' => \$ViewAfter, + 'a' => \$Anonymize, + 'I' => \$ImageDir, + 'e:s' => \$ExpenseReportCode, + 'h|help' => \$Help + ); + +# Help + +defined $Help && do { + print <<EOF; +Usage: GenerateLatexExpenseReport.pl [OPTION] -e ERCode + +Options: + -c Internal report + -m Suppress meals + -v View PDF on completion + -a Anonymous, omit header/footer + -I Image directory + -e ER Code (AISER0001) + +EOF + exit -1; +}; + +die "Pass -e <ExpenseReportCode>" unless defined $ExpenseReportCode; + +###################################################################### +# Report items + +my @ItemizedExpenses; +my $ItemizedTotal = 0.00; + +my @ItemizedReceipts; + +my @MealsReport; + +###################################################################### +# Gather required data about this expense report from the directory name +# ie: ./AISER0015 20090419-20090425 AGIL2078 Shands HACMP/ +# +# ExpenseReportCode = AISER0015 +# DateRange = 20090419-20090425 +# ProjectCode = AGIL2078 +# Description = Shands HACMP + + +###################################################################### +# Remaining options + +# Where is the ledger binary? +my $LedgerBin = "./ledger -f ./.ledger -V"; + +# -E Show empty accounts +# -S d Sort by date +# -V Convert mileage to $ +my $LedgerOpts = "--no-color -S d"; + +my $LedgerAcct = "^Dest:Projects"; + +my $LedgerCriteria = "%" . "ER=$ExpenseReportCode"; + +# Internal report? + +if ( $Internal ) { + + # No mileage on an internal report + # $LedgerCriteria .= "&!/Mileage/"; # This shouldn't matter, just don't put metadata for ER on mileage + $LedgerAcct = "^Dest:Internal"; + +} + +my $CmdLine = "$LedgerBin reg $LedgerOpts -E \"$LedgerCriteria\" and ^Stub " + . "--format \"%(tag('ER'))~%(tag('PROJECT'))~%(tag('NOTE'))\n\""; +### $CmdLine + +my @TempLine = `$CmdLine`; + +# Match all remaining items +$TempLine[0] =~ m,^(?<Er>.*?)~ + (?<Project>.*?)~ + (?<Note>.*?)\s*$,x; + +my $ProjectCode= $+{'Project'}; +my $Description= $+{'Note'}; + +### $ExpenseReportCode +### $ProjectCode +### $Description +### $LedgerAcct +### $Internal +### $Anonymize +### $LedgerAcct +### $LedgerOpts +### $LedgerCriteria + + +###################################################################### +# Pull main ledger report of line items for the expense report +# Using ~ as a delimiter +# +# Example: +# '2009/04/25~AR:Projects:AGIL2078:PersMealsLunch~:AISER0015: PILOT 00004259 MIDWAY, FL~ 8.68~Receipts/AGIL2078/20090425_Pilot_8_68_21204.jpg\n' +# +#./ledger --no-color reg %ER=AISER0040 and ^Projects -y "%Y/%m/%d" -V --format "%(date)~%(account)~%(payee)~%(amount)~%(tag('NOTE'))\n" + +$CmdLine = "$LedgerBin reg $LedgerOpts \"$LedgerCriteria\" and \"$LedgerAcct\" " + . "-y \"%Y/%m/%d\" " + . "--format \"%(date)~%(tag('CATEGORY'))~%(payee)~%(display_amount)~%(tag('NOTE'))~%(tag('RECEIPT'))\\n\""; +### $CmdLine +my @MainReport = `$CmdLine`; + + +### MainReport: @MainReport + +# Remove any project codes and linefeeds +#map { chomp(); s/(:AISER[0-9][0-9][0-9][0-9])+://g; } @MainReport; # No need, thats now metadata + +foreach my $line (@MainReport) { ### Processing Main Report... done + + # Remove bad chars (#&) + $line =~ tr/#&//d; + + # Match all remaining items + $line =~ m,^(?<Date>[0-9]{4}/[0-9]{2}/[0-9]{2})~ + (?<Category>.*?)~ + (?<Vendor>.*?)~ + (?<Amount>.*?)~ + (?<Note>.*?)~ + (?<Receipts>.*?)\s*$,x; + my %Record = %+; + + $Record{'Amount'}=~tr/$,//d; + + foreach (keys %Record) { + $Record{$_} =~ s/^\s+//g; + } + + + # Grab images from <<file:///dir/filename.jpg>> + my $ImageList = $Record{'Receipts'}; + $ImageList //= ''; + my @Images = split( /,/, $ImageList ); + + # Cleanup + # Take last word of account name as category + $Record{'Category'} = ( split( /:/, $Record{'Category'} ) )[-1]; + + # If no images, italicise the line item. + $Record{'Italics'} = 1; + + # Test images + foreach my $Image (@Images) { + + # Turn off italics because there is an image + $Record{'Italics'} = 0; + + if (! -r $ImageDir . "/" . $Image) { + print STDERR "Missing $ImageDir/$Image\n"; + } + } + + # Add to itemized expenses to be printed + push( @ItemizedExpenses, \%Record ); + $ItemizedTotal += $Record{'Amount'}; + + # Add to itemized reciepts for printing + push( @ItemizedReceipts, { 'Vendor' => $Record{'Vendor'}, + 'Amount' => $Record{'Amount'}, + 'Date' => $Record{'Date'}, + 'Images' => \@Images } ) + if $ImageList; + +} + +### @ItemizedExpenses + +###################################################################### +# Meals report + +# Summarize total spent on meals by day +$CmdLine = "$LedgerBin reg $LedgerOpts " + . "\"$LedgerCriteria\" and \"$LedgerAcct\" and \%CATEGORY=Meals " + . "-D -n " + . "--format \"%(account)~%(payee)~%(display_amount)~%(total)\n\" " + . "| grep -v '<None>'"; + +### $CmdLine +my @MealsOutput = `$CmdLine`; + +### @MealsOutput + +foreach my $line (@MealsOutput) { + + # Match all remaining items + $line =~ m,^(?<Account>.*?)~ + (?<DOW>.*?)~\$ + (?<Amount>\s*[0-9]+\.[0-9]+)~\$ + (?<RunningTotal>.*?)\s*$,x; + my %TRecord=%+; + $TRecord{'Account'}=~s/^Projects://g; + $TRecord{'DOW'}=~s/^- //g; + + # Add to itemized expenses to be printed + push( @MealsReport, \%TRecord ); + +} + +###################################################################### +# Total by category + +$CmdLine = "$LedgerBin bal $LedgerOpts " + . "\"$LedgerCriteria\" and \"$LedgerAcct\" '--account=tag(\"CATEGORY\")' " + . "--format \"%(account)~%(display_total)\\n\""; + +### $CmdLine +my @CategoryOutput = `$CmdLine`; + +### @CategoryOutput + +my @CategoryReport; + +foreach my $line (@CategoryOutput) { + + chomp $line; + $line =~ tr/\$,//d; + + # Match all remaining items + my @Temp = split(/~/,$line); + + my %TRecord= ( 'Category' => $Temp[0], + 'Amount' => $Temp[1]); + + if ($TRecord{'Category'} eq '') { + $TRecord{'Category'} = '\\hline \\bf Total'; + } + + # Cleanup + # Take last word of account name as category + $TRecord{'Category'} = ( split( /:/, $TRecord{'Category'} ) )[0]; + + # Add to itemized expenses to be printed + push( @CategoryReport, \%TRecord ); + +} + +### @CategoryReport + + +###################################################################### +# Output +###################################################################### + +my $TTVars = { + 'Internal' => $Internal, + 'SuppressMeals' => $SuppressMeals, + 'ExpenseReportCode' => $ExpenseReportCode, + 'ProjectCode' => $ProjectCode, + 'Description' => $Description, + 'ItemizedExpenses' => \@ItemizedExpenses, + 'ItemizedTotal' => $ItemizedTotal, + 'MealsReport' => \@MealsReport, + 'CategoryReport' => \@CategoryReport, + 'ItemizedReceipts' => \@ItemizedReceipts, + 'ImageDir' => $ImageDir, + 'Anonymize' => $Anonymize +}; + +#### $TTVars + +my $LatexTemplate = <<EOF; +[% USE format %][% ToDollars = format('\\\$%.2f') %] +%%%%%%%%%%% Header + +\\documentclass[10pt,letterpaper]{article} +\\usepackage[letterpaper,includeheadfoot,top=0.5in,bottom=0.5in,left=0.75in,right=0.75in]{geometry} +\\usepackage[utf8]{inputenc} +\\usepackage[T1]{fontenc} +\\usepackage[scaled]{helvet} +\\renewcommand*\\familydefault{\\sfdefault} +\\usepackage{lastpage} +\\usepackage{fancyhdr} +\\usepackage{graphicx} +\\usepackage{multicol} +\\usepackage[colorlinks,linkcolor=blue]{hyperref} +\\pagestyle{fancy} +\\renewcommand{\\headrulewidth}{1pt} +\\renewcommand{\\footrulewidth}{0.5pt} +\\geometry{headheight=48pt} + + +\\begin{document} + +%%%%%%%%%% Itemized table + +\\section{Itemized Expenses} +[% FOREACH Expense IN ItemizedExpenses %] +[% IF loop.first() or ( ( loop.count mod 20 ) == 0 ) %] +\\begin{tabular}{|l|l|p{2in}|r|p{2in}|} +\\hline +\\hline +\\bf Date & \\bf Category & \\bf Expense & \\bf Amount & \\bf Notes \\\\ +\\hline \\hline +[% END %] +[% IF Expense.Italics %]\\it[% END %] [% Expense.Date %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Category %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Vendor %] & [% IF Expense.Italics %]\\it[% END %] [% ToDollars(Expense.Amount) %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Note %] \\\\ \\hline +[% IF loop.last() %] +\\hline + & & \\bf Total & \\bf [% ToDollars(ItemizedTotal) %] & \\\\ +[% END %] +[% IF ( ( (loop.count + 1) mod 20 ) == 0 ) or loop.last() %] +\\hline +\\hline +\\end{tabular} +[% IF ( ( (loop.count + 1) mod 20 ) == 0 ) %]\\newline {\\it Continued on next page...}[% END %] +\\newpage +[% END %] +[% END %] + +[% IF ! Internal %][% IF ! SuppressMeals %] +%%%%%%%%%% Meals summary table + +\\section{Meals Summary By Day} + +\\begin{tabular}{|l|l|p{2in}|p{2in}|} +\\hline +\\hline +\\bf DOW & \\bf Daily Total & \\bf Running Total \\\\ +\\hline \\hline +[% FOREACH Meal IN MealsReport %] +[% Meal.DOW %] & [% ToDollars(Meal.Amount) %] & [% ToDollars(Meal.RunningTotal) %] \\\\ \\hline +[% END %] +\\hline +\\hline +\\end{tabular} +[% END %][% END %] + +%%%%%%%%%% Category summary + +\\section{Expenses Summary} + +\\begin{tabular}{|l|l|} +\\hline +\\hline +\\bf Category & \\bf Total \\\\ +\\hline \\hline +[% FOREACH Category IN CategoryReport %] +[% Category.Category %] & [% ToDollars(Category.Amount) %] \\\\ \\hline +[% END %] +\\hline +\\hline +\\end{tabular} + + +%%%%%%%%%% Begin receipts + +\\section{Scanned Receipts} + +[% FOREACH Receipt IN ItemizedReceipts %] +\\subsection{[% Receipt.Date %] [% Receipt.Vendor %]: [% ToDollars(Receipt.Amount) %]} +[% FOREACH Image IN Receipt.Images %] +\\includegraphics[angle=90,width=\\textwidth,keepaspectratio]{[% ImageDir %]/[% Image %]} \\\\ +[% END %] + +[% END %] + + +%%%%%%%%%% Footer + +\\end{document} +EOF + +my $LatexFN = $ExpenseReportCode . "-" . $ProjectCode . ".tex"; +### $LatexFN + +$TT->process( \$LatexTemplate, $TTVars, "./tmp/" . $LatexFN ) || do { + my $error = $TT->error(); + print "error type: ", $error->type(), "\n"; + print "error info: ", $error->info(), "\n"; + die $error; +}; + + +my $LatexOutput = `pdflatex -interaction batchmode -output-directory ./tmp "$LatexFN"`; +### $LatexOutput + + $LatexOutput = `pdflatex -interaction batchmode -output-directory ./tmp "$LatexFN"`; +### $LatexOutput + +if ($ViewAfter) { + my $ViewFN = $LatexFN; + $ViewFN =~ s/\.tex$/.pdf/; + `acroread "./tmp/$ViewFN"`; +} + diff --git a/contrib/raw/MetadataExample.dat b/contrib/raw/MetadataExample.dat new file mode 100644 index 00000000..791eaf77 --- /dev/null +++ b/contrib/raw/MetadataExample.dat @@ -0,0 +1,111 @@ +; TAG key: value
+
+2009/09/27 * (09/28/2009) HUDSON NEWS HOUSTN HBB HOUSTON, TX
+ Source:Visa -$6.55
+ Projects:Meals
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+
+2009/09/27 * (09/28/2009) PEET'S COFFEE & TEA KINGWOOD, TX
+ Source:Visa -$2.44
+ Projects:Meals
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+
+2009/09/28 * (09/29/2009) FUSIA NEW YORK, NY
+ Source:Visa -$15.25
+ Projects:Meals
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+
+
+2009/09/29 * (09/30/2009) BALUCHI'S NEW YORK, NY
+ Source:Visa
+ Projects:Meals $20.00
+ ; ER: AISER0033
+ ; PROJECT: PROJXXXX
+ Internal:Travel $10.44
+ ; ER: AISER0036
+ ; PROJECT: Internal
+
+
+2009/10/01 * Reimbursing AISER0036
+ Bank:AISChecking
+ ; ER: AISER0036
+ ; PROJECT: Internal
+ Source:Visa $10.44
+
+
+----------
+
+$ ./ledger -Ef AISER0033.dat bal '--account=tag("ER")'
+ $-44.24
+ $44.24 AISER0033
+ 0 AISER0036
+--------------------
+ 0
+
+$ ./ledger -Ef AISER0033.dat bal
+ $-10.44 Bank:AISChecking
+ $10.44 Internal:Travel
+ $44.24 Projects:Meals
+ $-44.24 Source:Visa
+--------------------
+ 0
+
+$ ./ledger -Ef AISER0033.dat bal '--account=tag("PROJECT")'
+ $-44.24
+ 0 Internal
+ $44.24 PROJXXXX
+--------------------
+ 0
+
+$ ./ledger -f AISER0033.dat reg '--account=tag("PROJECT")'
+09-Sep-27 HUDSON NEWS HOUSTN .. $-6.55 $-6.55
+09-Sep-27 HUDSON NEWS HOUSTN .. PROJXXXX $6.55 0
+09-Sep-27 PEET'S COFFEE & TEA.. $-2.44 $-2.44
+09-Sep-27 PEET'S COFFEE & TEA.. PROJXXXX $2.44 0
+09-Sep-28 FUSIA NEW YORK, NY $-15.25 $-15.25
+09-Sep-28 FUSIA NEW YORK, NY PROJXXXX $15.25 0
+09-Sep-29 BALUCHI'S NEW YORK,.. $-30.44 $-30.44
+09-Sep-29 BALUCHI'S NEW YORK,.. PROJXXXX $20.00 $-10.44
+09-Sep-29 BALUCHI'S NEW YORK,.. Internal $10.44 0
+09-Oct-01 Reimbursing AISER0036 Internal $-10.44 $-10.44
+09-Oct-01 Reimbursing AISER0036 $10.44 0
+
+$ ./ledger -f AISER0033.dat reg '--account=tag("ER")'
+09-Sep-27 HUDSON NEWS HOUSTN .. $-6.55 $-6.55
+09-Sep-27 HUDSON NEWS HOUSTN .. AISER0033 $6.55 0
+09-Sep-27 PEET'S COFFEE & TEA.. $-2.44 $-2.44
+09-Sep-27 PEET'S COFFEE & TEA.. AISER0033 $2.44 0
+09-Sep-28 FUSIA NEW YORK, NY $-15.25 $-15.25
+09-Sep-28 FUSIA NEW YORK, NY AISER0033 $15.25 0
+09-Sep-29 BALUCHI'S NEW YORK,.. $-30.44 $-30.44
+09-Sep-29 BALUCHI'S NEW YORK,.. AISER0033 $20.00 $-10.44
+09-Sep-29 BALUCHI'S NEW YORK,.. AISER0036 $10.44 0
+09-Oct-01 Reimbursing AISER0036 AISER0036 $-10.44 $-10.44
+09-Oct-01 Reimbursing AISER0036 $10.44 0
+
+
+$ ./ledger -f AISER0033.dat reg %ER=AISER0033
+09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
+09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
+09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
+09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
+
+
+$ ./ledger -f AISER0033.dat reg %ER=AISER0036
+09-Sep-29 BALUCHI'S NEW YORK,.. Internal:Travel $10.44 $10.44
+09-Oct-01 Reimbursing AISER0036 Bank:AISChecking $-10.44 0
+
+$ ./ledger -f AISER0033.dat reg %PROJECT=PROJXXXX
+09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
+09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
+09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
+09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
+
+$ ./ledger -f AISER0033.dat --prepend-format='%(tag("IMG")) ' reg %ER=0033 +image1.jpg 09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55 +image2.jpg 09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99 +image3.jpg 09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24 +image4.jpg 09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24 diff --git a/contrib/raw/README b/contrib/raw/README new file mode 100644 index 00000000..82ae74e1 --- /dev/null +++ b/contrib/raw/README @@ -0,0 +1,5 @@ +These scripts are from my (rladams) local ledger customizations. + +They are intended as examples for features that can be made generic to benefit other Ledger users. + +As they become refined, the raw files will be removed and replaced by suitable sources in contrib. diff --git a/contrib/raw/VerifyImages.sh b/contrib/raw/VerifyImages.sh new file mode 100755 index 00000000..5975f7cf --- /dev/null +++ b/contrib/raw/VerifyImages.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +grep -h '; RECEIPT: ' \ + *.dat \ + */*.dat \ + | sed 's,\W*; RECEIPT: ,,g' \ + | tr , '\n' \ + | sort -u \ + | while read X +do + [ -f "$X" ] \ + && echo OK $X \ + || echo XX $X +done diff --git a/contrib/raw/dotemacs.el b/contrib/raw/dotemacs.el new file mode 100644 index 00000000..b270042e --- /dev/null +++ b/contrib/raw/dotemacs.el @@ -0,0 +1,201 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Ledger + +;; Maybe later add this to the expense repo once it settles +(add-to-list 'load-path "/home/adamsrl/.emacs.d/addons/ledger") + +(add-to-list 'load-path "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/bin") +(autoload 'ledger-mode "ldg-new" nil t) +(add-to-list 'auto-mode-alist '("\\.dat$" . ledger-mode)) + +(add-hook 'ledger-mode-hook + (lambda () + (setq truncate-lines 1) + (url-handler-mode 1) ; Enable hyperlinks + (require 'ledger-matching) ; Requires ldg-report anyway + (load-file "/home/adamsrl/.emacs.d/addons/ledger/ldg-xact.el") + (let ((map (current-local-map))) + (define-key map (kbd "\C-c o") 'find-file-at-point) ; Open images + (define-key map (kbd "<f8>") 'ledger-expense-shortcut) + (define-key map (kbd "M-i") 'ledger-expense-internal) + (define-key map (kbd "M-o") 'ledger-expense-personal) + (define-key map (kbd "M-'") 'ledger-expense-split) + (define-key map (kbd "M-n") '(lambda () + (interactive) + (ledger-post-next-xact) + (recenter) + (when (get-buffer "*Receipt*") + (ledger-expense-show-receipt)))) + (define-key map (kbd "M-p") '(lambda () (interactive) + (ledger-post-prev-xact) + (recenter) + (when (get-buffer "*Receipt*") + (ledger-expense-show-receipt)))) + (local-unset-key [tab]) ; Ideally this turns off pcomplete + (local-unset-key [(control ?i)]) ; Ideally this turns off pcomplete + ) + + ;(defface ledger-report-face-account-ok '((t (:foreground "Cyan"))) "Derp") + ;(defface ledger-report-face-account-bad '((t (:foreground "Red"))) "Derp") + + (font-lock-add-keywords + 'ledger-mode + '(("Unassigned\\|Unknown\\|; RECEIPT:$" 0 'highlight prepend))) )) + +;; My customizations to make receipt image matching work with ledger-report mode +(add-hook 'ledger-report-mode-hook + (lambda () + (hl-line-mode 1) + (local-set-key (kbd "<RET>") 'ledger-report-visit-source) ; Make return jump to the right txn + (local-set-key (kbd "<tab>") 'ledger-report-visit-source) ; Make tab jump to the right txn + (local-set-key (kbd "n") '(lambda () + (interactive) + (save-selected-window + (next-line) + (ledger-report-visit-source)))) ; Update a txn window but keep focus + (local-set-key (kbd "p") '(lambda () + (interactive) + (save-selected-window + (previous-line) + (ledger-report-visit-source)))) ; Update a txn window but keep focus + + + (local-set-key (kbd "M-r") 'ledger-receipt-matching) ; Link receipt to current item + (local-set-key (kbd "M-l") 'ledger-matching-tie-receipt-to-txn) ; Link receipt to current item + (local-set-key (kbd "M-n") '(lambda () + (interactive) + (ledger-matching-image-offset-adjust 1))) ; Next receipt image + (local-set-key (kbd "M-p") '(lambda () + (interactive) + (ledger-matching-image-offset-adjust -1))) ; prev receipt image + (local-set-key (kbd "M-s") '(lambda () + (interactive) + (ledger-receipt-skip))) ; Skip receipt image + (local-set-key (kbd "C-c C-e") '(lambda () (interactive) + (save-selected-window + (ledger-report-visit-source) + (ledger-toggle-current-entry) ))) ; Toggle entry + )) + +(defvar *ledger-expense-shortcut-ER* + "Current expense report number, just last four digits (ie: 1234 results in AISER1234).") + +(defvar *ledger-expense-shortcut-split-ER* + "Split (ie: internal) expense report number, just last four digits (ie: 1234 results in AISER1234).") + +(defvar *ledger-expense-shortcut-Proj* "" + "Current export report project code (ie: AGIL1292)") + +(defun ledger-expense-shortcut-ER-format-specifier () *ledger-expense-shortcut-ER*) + +(defun ledger-expense-shortcut-setup (ER Split Proj) + "Sets the variables expanded into the transaction." + (interactive "MER Number (4 digit number only): \nMSplit ER Number (4 digit number only): \nMProject: ") + (setq *ledger-expense-shortcut-ER* + (concatenate 'string "AISER" ER)) + (setq *ledger-expense-shortcut-split-ER* + (concatenate 'string "AISER" Split)) + (setq *ledger-expense-shortcut-Proj* Proj) + (setq ledger-matching-project Proj) + (message "Set Proj to %s and ER to %s, split to %s" + *ledger-expense-shortcut-Proj* + *ledger-expense-shortcut-ER* + *ledger-expense-shortcut-split-ER*)) + +(defun ledger-expense-shortcut () + "Updates the ER and Project metadata with the current values of the shortcut variables." + (interactive) + (when (eq major-mode 'ledger-mode) + (if (or (eql *ledger-expense-shortcut-ER* "") + (eql *ledger-expense-shortcut-Proj* "")) + (message "Run ledger-expense-shortcut-setup first.") + (save-excursion + (search-forward "; ER:") + (kill-line nil) + (insert " " *ledger-expense-shortcut-ER*)) + (save-excursion + (search-forward "; PROJECT:") + (kill-line nil) + (insert " " *ledger-expense-shortcut-Proj*))))) + +(defun ledger-expense-split () + "Splits the current transaction between internal and projects." + (interactive) + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (re-search-forward "^ +Dest:Projects") + (move-beginning-of-line nil) + (let ((begin (point)) + (end (re-search-forward "^$"))) + (goto-char end) + (insert (buffer-substring begin end)) + (goto-char end) + (re-search-forward "^ Dest:Projects") + (replace-match " Dest:Internal") + (re-search-forward "; ER: +[A-Za-z0-9]+") + (replace-match (concat "; ER: " *ledger-expense-shortcut-split-ER*) t) + (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t) + (replace-match "; CATEGORY: Travel" t)))) + (re-search-backward "^[0-9]\\{4\\}/") + (re-search-forward "^ +Dest:Projects") + (insert-string " $") )) + +(defun ledger-expense-internal () + "Makes the expense an internal one." + (interactive) + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (let ((begin (point)) + (end (save-excursion (re-search-forward "^$")))) + (when (re-search-forward "^ Dest:Projects" end t) + (replace-match " Dest:Internal") ) + (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t) + (replace-match "; CATEGORY: Travel" t)))))) + +(defun ledger-expense-personal () + "Makes the expense an personal one, eliminating metadata and receipts." + (interactive) + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (let ((begin (point)) + (end (save-excursion (re-search-forward "^$")))) + (when (re-search-forward "^ Dest:Projects" end t) + (replace-match " Other:Personal")) + (goto-char begin) + (save-excursion + (when (re-search-forward "^ +; ER:" end t) + (beginning-of-line) + (kill-line 1))) + (save-excursion + (when (re-search-forward "^ +; PROJECT:" end t) + (beginning-of-line) + (kill-line 1))) + (save-excursion + (when (re-search-forward "^ +; CATEGORY:" end t) + (beginning-of-line) + (kill-line 1))) + (save-excursion + (when (re-search-forward "^ +; RECEIPT:" end t) + (beginning-of-line) + (kill-line 1))) + (ledger-toggle-current-entry))))) + +(defun ledger-expense-show-receipt () + "Uses the Receipt buffer to show the receipt of the txn we're on." + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (let ((begin (point)) + (end (save-excursion (re-search-forward "^$")))) + (save-excursion + (when (re-search-forward "^\\( +; RECEIPT: +\\)\\([^,]+?.jpg\\).*$" end t) + (ledger-matching-display-image + (concat "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/" + (match-string 2))) )))))) diff --git a/contrib/raw/ledger-matching.el b/contrib/raw/ledger-matching.el new file mode 100644 index 00000000..7c568126 --- /dev/null +++ b/contrib/raw/ledger-matching.el @@ -0,0 +1,342 @@ +;; This library is intended to allow me to view a receipt on one panel, and tie it to ledger transactions in another + +(require 'ldg-report) + +(defgroup ledger-matching nil + "Ledger image matching") + +(defcustom ledger-matching-sourcedir "~/AdamsInfoServ/BusinessDocuments/Ledger/Incoming" + "Source directory for images to process, ie: the incoming queue of images." + :group 'ledger-matching) + +(defcustom ledger-matching-destdir "~/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/Receipts" + "Destination directory for images when matched, will still have a project directory appended to it." + :group 'ledger-matching) + +(defcustom ledger-matching-relative-receipt-dir "Receipts" + "Relative directory root for destination images used in Ledger entries, will have the project directory appended and receipt filename." + :group 'ledger-matching) + +(defcustom ledger-matching-convert-binary "/usr/bin/convert" + "Path to the Imagemagick convert command." + :group 'ledger-matching) + +(defcustom ledger-matching-scale 50 + "Scaling parameter to Imagemagick's convert to resize an image for viewing." + :group 'ledger-matching) + +(defcustom ledger-matching-rotation 0 + "Rotation parameter to Imagemagick's convert to rotate an image for viewing. Images on disk should always be upright for reading." + :group 'ledger-matching) + + +(defconst ledger-matching-image-buffer "*Receipt*" + "Buffer name we load images into. Created if it doesn't exist, and persists across image loads.") + + +(defvar ledger-matching-project "Internal" + "The directory appended to the destination for the project code where receipts will be stored.") + +(defvar ledger-matching-image-offset 0 + "The index of the current file from the SORTED source directory contents.") + +(defvar ledger-matching-image-name nil + "The filename only of the current image.") + + +(defun ledger-matching-display-image (image-filename) + "Resize the image and load it into our viewing buffer." + + ;; Create our viewing buffer if needed, and set it. Do NOT switch, + ;; this buffer isn't the primary. Let the user leave it where they + ;; place it. + (unless (get-buffer ledger-matching-image-buffer) + (get-buffer-create ledger-matching-image-buffer)) + (set-buffer ledger-matching-image-buffer) + (erase-buffer) + (goto-char (point-min)) + (insert-string image-filename "\n") + + ;; Convert the source to the temporary dest applying resizing and rotation + (let* ((source (expand-file-name image-filename ledger-matching-sourcedir)) + (dest (make-temp-file "ledger-matching-" nil ".jpg")) + (result (call-process ledger-matching-convert-binary nil (get-buffer "*Messages*") nil + source + "-scale" (concat (number-to-string ledger-matching-scale) "%") + "-rotate" (number-to-string ledger-matching-rotation) + dest))) + + (if (/= 0 result) + + ;; Bomb out if the convert fails + (message "Error running convert, see *Messages* buffer for details.") + + ;; Insert scaled image into the viewing buffer, replacing + ;; current contents Temp buffer is to force sync reading into + ;; memory of the jpeg due to async race condition with display + ;; and file deletion + (let ((image (create-image (with-temp-buffer + (insert-file-contents-literally dest) + (string-as-unibyte (buffer-string))) + 'jpeg t))) + (insert-image image) + (goto-char (point-min)) + + ;; Redisplay is required to prevent a race condition between displaying the image and the deletion. Apparently its async. + ;; Either redisplay or the above string method work, both together can't hurt. + (redisplay) + )) + + ;; Delete our temporary file + (delete-file dest))) + + + +(defun ledger-matching-update-current-image () + "Grab the image from the source directory by offset and display" + + (let* ((file-listing (directory-files ledger-matching-sourcedir nil "\.jpg$" nil)) + (len (safe-length file-listing))) + + ;; Ensure our offset doesn't exceed the file list + (cond ((= len 0) + (message "No files found in source directory.")) + + ((< len 0) + (message "Error, list of files should never be negative. Epic fail.")) + + ((>= ledger-matching-image-offset len) + (message "Hit end of list. Last image.") + (setq ledger-matching-image-offset (1- len))) + + ((< ledger-matching-image-offset 0) + (message "Beginning of list. First image.") + (setq ledger-matching-image-offset 0))) + + ;; Get the name for the offset + (setq ledger-matching-image-name (nth ledger-matching-image-offset file-listing)) + + (ledger-matching-display-image ledger-matching-image-name))) + + + +(defun ledger-matching-image-offset-adjust (amount) + "Incr/decr the offset and update the receipt buffer." + + (setq ledger-matching-image-offset (+ ledger-matching-image-offset amount)) + (ledger-matching-update-current-image)) + + + +(defun ledger-receipt-matching () + "Open the receipt buffer and start with the first image." + (interactive) + (setq ledger-matching-image-offset 0) + (ledger-matching-update-current-image)) + + + +(defun ledger-matching-tie-receipt-to-txn () + (interactive) + (save-selected-window + (ledger-report-visit-source) + + ;; Assumes we're in a narrowed buffer with ONLY this txn + (backward-paragraph) + (beginning-of-line) + + ;; ;; Update the ER and Project while I'm there + ;; (save-excursion + ;; (search-forward "; ER:") + ;; (kill-line nil) + ;; (insert " " *ledger-expense-shortcut-ER*)) + ;; Just do the project for now. + (save-excursion + (search-forward "; PROJECT:") + (kill-line nil) + (insert " " *ledger-expense-shortcut-Proj*)) + + ;; Goto the receipt line, unless their isn't one then add one + (unless (search-forward "RECEIPT:" nil t) + + ;; Still at date line if that failed + (next-line) + (newline) + (insert-string " ; RECEIPT:")) + + ;; Point immediately after : on tag + + ;; Check for existing jpg file + (if (search-forward ".jpg" (line-end-position) t) + + ;; if present make it a comma delimited list + (insert-string ",") + + ;; otherwise just add a space to pad + (insert-string " ")) + + ;; Add our relative filename as the value of the RECEIPT tag + (insert-string (concat ledger-matching-relative-receipt-dir "/" + ledger-matching-project "/" + ledger-matching-image-name)) + + ;; Create the destination project dir if it doesn't exist. + (let ((full-destination (concat ledger-matching-destdir "/" ledger-matching-project ))) + (unless (file-accessible-directory-p full-destination) + (make-directory full-destination t))) + + ;; Rename the file from the source directory to its permanent home + (rename-file (concat ledger-matching-sourcedir "/" + ledger-matching-image-name) + (concat ledger-matching-destdir "/" + ledger-matching-project "/" + ledger-matching-image-name)) + + ;; Update the receipt screen + (ledger-matching-update-current-image) + + (message "Filed %s to project %s" ledger-matching-image-name ledger-matching-project))) + + + +(defun ledger-receipt-skip () + "Move the current image to the Skip directory because its not relevant." + + (rename-file (concat ledger-matching-sourcedir "/" + ledger-matching-image-name) + (concat ledger-matching-sourcedir "/Skip/" + ledger-matching-image-name)) + + ;; Update the receipt screen at the same offset + (ledger-matching-update-current-image)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Items below are speed entry macros, and should eventually migrate to their own file. + +(defvar *ledger-expense-shortcut-ER* + "Current expense report number, just last four digits (ie: 1234 results in AISER1234).") + +(defvar *ledger-expense-shortcut-split-ER* + "Split (ie: internal) expense report number, just last four digits (ie: 1234 results in AISER1234).") + +(defvar *ledger-expense-shortcut-Proj* "" + "Current export report project code (ie: AGIL1292)") + +(defun ledger-expense-shortcut-ER-format-specifier () *ledger-expense-shortcut-ER*) + +(defun ledger-expense-shortcut-Project-format-specifier () *ledger-expense-shortcut-Proj*) + +(defun ledger-expense-shortcut-setup (ER Split Proj) + "Sets the variables expanded into the transaction." + (interactive "MER Number (ER or IN and 4 digit number only): \nMSplit ER Number (ER or IN and 4 digit number only): \nMProject: ") + (setq *ledger-expense-shortcut-ER* + (concatenate 'string "AIS" ER)) + (setq *ledger-expense-shortcut-split-ER* + (concatenate 'string "AIS" Split)) + (setq *ledger-expense-shortcut-Proj* Proj) + (setq ledger-matching-project Proj) + (message "Set Proj to %s and ER to %s, split to %s" + *ledger-expense-shortcut-Proj* + *ledger-expense-shortcut-ER* + *ledger-expense-shortcut-split-ER*)) + +(defun ledger-expense-shortcut () + "Updates the ER and Project metadata with the current values of the shortcut variables." + (interactive) + (when (eq major-mode 'ledger-mode) + (if (or (eql *ledger-expense-shortcut-ER* "") + (eql *ledger-expense-shortcut-Proj* "")) + (message "Run ledger-expense-shortcut-setup first.") + (save-excursion + (search-forward "; ER:") + (kill-line nil) + (insert " " *ledger-expense-shortcut-ER*)) + (save-excursion + (search-forward "; PROJECT:") + (kill-line nil) + (insert " " *ledger-expense-shortcut-Proj*))))) + +(defun ledger-expense-split () + "Splits the current transaction between internal and projects." + (interactive) + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (re-search-forward "^ +Dest:Projects") + (move-beginning-of-line nil) + (let ((begin (point)) + (end (re-search-forward "^$"))) + (goto-char end) + (insert (buffer-substring begin end)) + (goto-char end) + (re-search-forward "^ Dest:Projects") + (replace-match " Dest:Internal") + (re-search-forward "; ER: +[A-Za-z0-9]+") + (replace-match (concat "; ER: " *ledger-expense-shortcut-split-ER*) t) + (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t) + (replace-match "; CATEGORY: Travel" t)))) + (re-search-backward "^[0-9]\\{4\\}/") + (re-search-forward "^ +Dest:Projects") + (insert-string " $") )) + +(defun ledger-expense-internal () + "Makes the expense an internal one." + (interactive) + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (let ((begin (point)) + (end (save-excursion (re-search-forward "^$")))) + (when (re-search-forward "^ Dest:Projects" end t) + (replace-match " Dest:Internal") ) + (when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t) + (replace-match "; CATEGORY: Travel" t)))))) + +(defun ledger-expense-personal () + "Makes the expense an personal one, eliminating metadata and receipts." + (interactive) + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (let ((begin (point)) + (end (save-excursion (re-search-forward "^$")))) + (when (re-search-forward "^ Dest:Projects" end t) + (replace-match " Other:Personal")) + (goto-char begin) + (save-excursion + (when (re-search-forward "^ +; ER:" end t) + (beginning-of-line) + (kill-line 1))) + (save-excursion + (when (re-search-forward "^ +; PROJECT:" end t) + (beginning-of-line) + (kill-line 1))) + (save-excursion + (when (re-search-forward "^ +; CATEGORY:" end t) + (beginning-of-line) + (kill-line 1))) + (save-excursion + (when (re-search-forward "^ +; RECEIPT:" end t) + (beginning-of-line) + (kill-line 1))) + (ledger-toggle-current-entry))))) + +(defun ledger-expense-show-receipt () + "Uses the Receipt buffer to show the receipt of the txn we're on." + (when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode + (save-excursion + (end-of-line) + (re-search-backward "^[0-9]\\{4\\}/") + (let ((begin (point)) + (end (save-excursion (re-search-forward "^$")))) + (save-excursion + (when (re-search-forward "^\\( +; RECEIPT: +\\)\\([^,]+?.jpg\\).*$" end t) + (ledger-matching-display-image + (concat "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/" + (match-string 2))) )))))) + + +(provide 'ledger-matching) diff --git a/contrib/raw/ledger-shell-environment-functions b/contrib/raw/ledger-shell-environment-functions new file mode 100644 index 00000000..7746dc41 --- /dev/null +++ b/contrib/raw/ledger-shell-environment-functions @@ -0,0 +1,90 @@ +# Environment for ledger expenses + +[ $(whoami) == "adamsrl" ] \ + && export LEDGER_HOME="/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell" \ + || export LEDGER_HOME="/home/Heather/AdamsRussell" + +[ $(hostname) == "cardamom" ] \ + && export LEDGER_BIN="${LEDGER_HOME}/ledger" \ + || export LEDGER_BIN="${LEDGER_HOME}/ledger.exe" + +[ $(whoami) == "andersonll" ] \ + && export LEDGER_HOME="/home/andersonll/AdamsInfoServ/Expenses" \ + && export LEDGER_BIN="${LEDGER_HOME}/ledger" + +# Common reports + +alias ledger='${LEDGER_BIN} -f "${LEDGER_HOME}/.ledger" -VE ' +alias ERSummary='ledger --pivot ER bal | egrep "AIS(ER|IN)[0-9]+|Unassigned"' + +function ERTxns() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + ledger reg "%ER=${1}" +} + +function ERCategorySummary() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + ledger bal --pivot CATEGORY "%ER=${1}" +} + +function ERMealSummary() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + ledger reg "%ER=${1}" and %CATEGORY=Meals -D +} + +function ERMeals() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + ledger reg "%ER=${1}" and %CATEGORY=Meals +} + +function ERUncleared() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + ledger reg "%ER=${1}" -U +} + +function ERMissingReceipts() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + ledger reg "%ER=${1}" and not %RECEIPT +} + +function ERVerify() { + [ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return + + echo "========== Uncleared txns below ==========" + ERUncleared "$1" + echo "========== Missing receipts below (miles and stubs ok) ==========" + ERMissingReceipts "$1" + echo "========== Category Summary (airline? mileage? car? hotel? ==========" + ERCategorySummary "$1" + echo "========== Meal summary (<\$50 / day unless otherwise specified) ==========" + ERMealSummary "$1" + echo "========== Account Verification (Internal vs Project ER should be ONE type) ==========" + echo $1 | grep AISIN >/dev/null 2>&1 \ + || { ledger reg "%ER=${1}" | grep Dest:Internal ; } \ + && { ledger reg "%ER=${1}" | grep Dest:Projects ; } + echo "========== Project Verification (only one project code should be listed) ==========" + ledger print "%ER=${1}" | grep PROJECT | sort -u + echo "========== Receipts missing ==========" + ledger print "%ER=${1}" | grep -h '; RECEIPT: ' \ + | sed 's,\W*; RECEIPT: ,,g' \ + | tr , '\n' \ + | sort -u \ + | while read X ; do + [ -f "${LEDGER_HOME}/${X}" ] \ + || echo XX $X + done +} + +function ERListing() { + ledger reg Stub --register-format="%(tag('ER')) %(tag('NOTE'))\n" | sort -u +} + +function ERQueue() { + ledger reg %ER=Unassigned --prepend-format="%(filename) " +} diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index e69de29b..12f96444 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -0,0 +1,105 @@ +# The following will be generated or updated when the 'doc' target is built: +# • user guide and man pages: if BUILD_DOCS is set +# • HTML versions of the above: if BUILD_DOCS and BUILD_WEB_DOCS are set +# • Doxygen / reference documentation: if USE_DOXYGEN is set + +######################################################################## + +if (USE_DOXYGEN) + find_package(Doxygen) + if(NOT DOXYGEN_FOUND) + message(FATAL_ERROR "Could not find doxygen. Reference documentation cannot be built.") + endif() + + configure_file(Doxyfile.in Doxyfile @ONLY) + + # see INPUT/FILE_PATTERNS in Doxyfile.in + file(GLOB doxygen_input_files ${CMAKE_SOURCE_DIR}/src/*.h) + + add_custom_command(OUTPUT html/index.html + COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile + DEPENDS Doxyfile ${doxygen_input_files} + COMMENT "Building doxygen documentation") + add_custom_target(doc.doxygen DEPENDS html/index.html) +else() + add_custom_target(doc.doxygen) +endif() + +######################################################################## + +if(NOT BUILD_DOCS) + add_custom_target(doc DEPENDS doc.doxygen) + return() +endif() + +set(info_files ledger.texi ledger3.texi ledger-mode.texi) + +find_program(MAKEINFO makeinfo) +find_program(TEXI2PDF texi2pdf) +find_program(MAN2HTML man2html) + +######################################################################## + +foreach(file ${info_files}) + get_filename_component(file_base ${file} NAME_WE) + if(BUILD_WEB_DOCS) + if(NOT MAKEINFO) + message(FATAL_ERROR "Could not find makeinfo. HTML version of documentation cannot be built.") + endif() + + add_custom_command(OUTPUT ${file_base}.html + COMMAND makeinfo --force --html --no-split -o ${file_base}.html ${CMAKE_CURRENT_SOURCE_DIR}/${file} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file} + VERBATIM) + list(APPEND ledger_doc_files ${file_base}.html) + endif(BUILD_WEB_DOCS) + + if(NOT TEXI2PDF) + message(WARNING "Could not find texi2pdf. PDF version of documentation will not be built.") + else() + add_custom_command(OUTPUT ${file_base}.pdf + COMMAND texi2pdf -b -q -o ${file_base}.pdf ${CMAKE_CURRENT_SOURCE_DIR}/${file} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file} + VERBATIM) + list(APPEND ledger_doc_files ${file_base}.pdf) + endif() +endforeach() + +######################################################################## + +if(BUILD_WEB_DOCS) + include(FindUnixCommands) + if(NOT BASH) + message(FATAL_ERROR "Could not find bash. Unable to build documentation.") + endif() + if(NOT MAN2HTML) + message(FATAL_ERROR "Could not find man2html. HTML version of man page cannot be built.") + endif() + + add_custom_command(OUTPUT ledger.1.html + COMMAND ${BASH} -c "man2html ${CMAKE_CURRENT_SOURCE_DIR}/ledger.1 | tail -n+3 > ledger.1.html" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ledger.1 + VERBATIM) + list(APPEND ledger_doc_files ledger.1.html) +endif(BUILD_WEB_DOCS) + +######################################################################## + +add_custom_target(doc DEPENDS ${ledger_doc_files} doc.doxygen) + +######################################################################## + +include(GNUInstallDirs) + +if(CMAKE_INSTALL_MANDIR) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/ledger.1 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 COMPONENT doc) +endif(CMAKE_INSTALL_MANDIR) + +if(CMAKE_INSTALL_DOCDIR) + foreach(file ${info_files}) + get_filename_component(file_base ${file} NAME_WE) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${file_base}.pdf + DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc OPTIONAL) + endforeach() +endif(CMAKE_INSTALL_DOCDIR) diff --git a/doc/Doxyfile b/doc/Doxyfile.in index d59d3f82..e340d84a 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile.in @@ -1,89 +1,103 @@ -# Doxyfile 1.5.7.1 +# Doxyfile 1.8.3 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project +# doxygen (www.doxygen.org) for a project. # -# All text after a hash (#) is considered a comment and will be ignored +# All text after a hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") +# Values that contain spaces should be placed between quotes (" "). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. PROJECT_NAME = Ledger -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 3.0 -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. -OUTPUT_DIRECTORY = %builddir%/doc +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, -# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, -# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, -# Spanish, Swedish, and Ukrainian. +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ @@ -98,130 +112,168 @@ ABBREVIATE_BRIEF = "The $name class" \ an \ the -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = YES -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = YES -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = %srcdir%/src/ - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/src/ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = YES -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO -# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. -ALIASES = +ALIASES = -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented classes, +# or namespaces to their corresponding documentation. Such a link can be +# prevented in individual cases by by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES @@ -231,473 +283,559 @@ BUILTIN_STL_SUPPORT = YES CPP_CLI_SUPPORT = NO -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES (the +# default) will make doxygen replace the get and set methods by a property in +# the documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penality. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = YES -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = YES -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = YES -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the +# Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if section-label ... \endif +# and \cond section-label ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - # Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the +# This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. This will remove the Namespaces entry from the Quick Index +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command <command> <input-file>, where <command> is the value of -# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file -# provided by doxygen. Whatever the program writes to standard output +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by -# doxygen. The layout file controls the global structure of the generated output files -# in an output format independent way. The create the layout file that represents -# doxygen's defaults, run doxygen with the -l option. You can optionally specify a -# file name after the option, if omitted DoxygenLayout.xml will be used as the name -# of the layout file. +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. -LAYOUT_FILE = +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. Do not use +# file names with spaces, bibtex cannot handle them. + +CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated +# The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. # jww (2009-01-31): Enable this toward the end -#WARN_IF_UNDOCUMENTED = YES WARN_IF_UNDOCUMENTED = NO -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = YES -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written # to stderr. -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = src +# please update dependencies in CMakeList.txt if you change this +INPUT = @PROJECT_SOURCE_DIR@/src -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl +# please update dependencies in CMakeList.txt if you change this FILE_PATTERNS = *.h -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. -EXCLUDE = +EXCLUDE = -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see # the \include command). -EXAMPLE_PATH = +EXAMPLE_PATH = -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see # the \image command). -IMAGE_PATH = +IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command <filter> <input-file>, where <filter> -# is the value of the INPUT_FILTER tag, and <input-file> is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be # ignored. -INPUT_FILTER = +INPUT_FILTER = -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. -FILTER_PATTERNS = +FILTER_PATTERNS = -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MD_FILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page (index.html). +# This can be useful if you have a project on for instance GitHub and want reuse +# the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES -# Setting the INLINE_SOURCES tag to YES will include the body +# Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES @@ -705,20 +843,21 @@ REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentstion. +# link to the source code. +# Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES @@ -727,130 +866,201 @@ VERBATIM_HEADERS = YES # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a # standard footer. -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). - -HTML_DYNAMIC_SECTIONS = NO - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. GENERATE_DOCSET = NO -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be # written to the html output directory. -CHM_FILE = +CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. -HHC_LOCATION = +HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO @@ -859,235 +1069,396 @@ GENERATE_CHI = NO # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members +# The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER -# are set, an additional index file will be generated that can be used as input for -# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated -# HTML documentation. +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. -QCH_FILE = +QCH_FILE = -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# <a href="http://doc.trolltech.com/qthelpproject.html#namespace">Qt Help Project / Namespace</a>. +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# <a href="http://doc.trolltech.com/qthelpproject.html#virtual-folders">Qt Help Project / Virtual Folders</a>. +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file . +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters -QHG_LOCATION = +QHP_CUST_FILTER_NAME = -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> +# Qt Help Project / Custom Filters</a>. -DISABLE_INDEX = NO +QHP_CUST_FILTER_ATTRS = -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> +# Qt Help Project / Filter Attributes</a>. -ENUM_VALUES_PER_LINE = 4 +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. -# If the tag value is set to FRAME, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. Other possible values -# for this tag are: HIERARCHIES, which will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list; -# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which -# disables this behavior completely. For backwards compatibility with previous -# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE -# respectively. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = YES -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# thA MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and +# SVG. The default value is HTML-CSS, which is slower, but has the best +# compatibility. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. +# There are two flavours of web server based search depending on the +# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for +# searching and an index file used by the script. When EXTERNAL_SEARCH is +# enabled the indexing and searching needs to be provided by external tools. +# See the manual for details. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain +# the search results. Doxygen ships with an example indexer (doxyindexer) and +# search engine (doxysearch.cgi) which are based on the open source search engine +# library Xapian. See the manual for configuration details. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will returned the search results when EXTERNAL_SEARCH is enabled. +# Doxygen ships with an example search engine (doxysearch) which is based on +# the open source search engine library Xapian. See the manual for configuration +# details. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. + +SEARCHDATA_FILE = searchdata.xml + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through other +# doxygen projects that are not otherwise connected via tags files, but are +# all added to the same search index. Each project needs to have a tag file set +# via GENERATE_TAGFILE. The search mapping then maps the name of the tag file +# to a relative location where the documentation can be found, +# similar to the +# TAGFILES option but without actually processing the tag file. +# The format is: EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... + +EXTRA_SEARCH_MAPPINGS = + #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = YES -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. LATEX_CMD_NAME = latex -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = letter -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. -EXTRA_PACKAGES = +EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! -LATEX_HEADER = +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = -# Set optional variables used in the generation of an rtf document. +# Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man -# The MAN_EXTENSION tag determines the extension that is added to +# The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO @@ -1096,33 +1467,33 @@ MAN_LINKS = NO # configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the # syntax of the XML files. -XML_SCHEMA = +XML_SCHEMA = -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the # syntax of the XML files. -XML_DTD = +XML_DTD = -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES @@ -1131,10 +1502,10 @@ XML_PROGRAMLISTING = YES # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO @@ -1143,343 +1514,364 @@ GENERATE_AUTOGEN_DEF = NO # configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. This is useful -# if you want to understand what is going on. On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- -# Configuration options related to the preprocessor +# Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = YES -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by # the preprocessor. -INCLUDE_PATH = +INCLUDE_PATH = -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED = -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::additions related to external references +# Configuration::additions related to external references #--------------------------------------------------------------------------- -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. -GENERATE_TAGFILE = +GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES -# The PERL_PATH should be the absolute path and name of the perl script +# The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. -MSCGEN_PATH = +MSCGEN_PATH = -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. DOT_FONTNAME = FreeSans -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 11 -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. -DOT_FONTPATH = +DOT_FONTPATH = -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO -# If set to YES, the inheritance and collaboration graphs will show the +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = YES -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = jpg -# The tag DOT_PATH can be used to specify the path where the dot tool can be +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. -DOT_PATH = +DOT_PATH = -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the # \dotfile command). -DOTFILE_DIRS = +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 1000 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- - -# The SEARCHENGINE tag specifies whether or not a search engine should be -# used. If set to NO the values of all tags below this one will be ignored. - -SEARCHENGINE = NO diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 64a271fa..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -# quick doc-building makefile used by website -# requires: man2html, texinfo - -docs: ledger.1.html ledger.html ledger.pdf ledger3.html ledger3.pdf - -%.1.html: %.1 - -man2html $< | tail -n+3 >$@ - -%.html: %.texi - -makeinfo --force --html --no-split -o $@ $< - -%.pdf: %.texi - -texi2pdf -b -q $< - rm -f $*.aux $*.cp $*.fn $*.ky $*.log $*.pg $*.toc $*.tp $*.vr - -clean: - rm -rf ledger.1.html ledger.html ledger3.html ledger.pdf ledger3.pdf diff --git a/doc/ledger-mode.texi b/doc/ledger-mode.texi new file mode 100644 index 00000000..d7144112 --- /dev/null +++ b/doc/ledger-mode.texi @@ -0,0 +1,769 @@ +\input texinfo @c -*-texinfo-*- +@setfilename ledger-mode.info +@settitle Ledger: Command-Line Accounting + +@dircategory Major Modes +@copying +Copyright (c) 2013, Craig Earls. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of New Artisans LLC nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +@end copying + +@documentencoding UTF-8 + +@iftex +@finalout +@end iftex + + +@titlepage +@title Ledger Mode +@subtitle Emacs Support For Version 3.0 of Ledger +@author Craig Earls +@end titlepage + +@direntry +* Ledger Mode: (ledger-mode). Command-Line Accounting +@end direntry + +@contents + +@ifnottex +@node Top, Copying, (dir), (dir) +@top Overview +Ledger is a command line accounting tool that provides double-entry +accounting based on a text journal. It provides no bells or whistles, +and returns the user to the days before user interfaces were even a +1twinkling in their father's CRT. + +Ledger Mode assists you in maintaining input files for Ledger, running +reports and much more... +@c @insertcopying +@end ifnottex + +@menu +* Copying:: +* Introduction to Ledger Mode:: +* The Ledger Buffer:: +* The Reconcile Buffer:: +* The Report Buffer:: +* Customizing Ledger-mode:: +* Generating Ledger Regression Tests:: +* Embedding Example results in Ledger Documentation:: +* Hacking Ledger-mode:: +@end menu + +@node Copying, Introduction to Ledger Mode, Top, Top +@chapter Copying +@insertcopying + +@node Introduction to Ledger Mode, The Ledger Buffer, Copying, Top +@chapter Introduction to Ledger Mode +@menu +* Quick Installation:: +* Menus:: +* Quick Demo:: +@end menu + +@node Quick Installation, Menus, Introduction to Ledger Mode, Introduction to Ledger Mode +@section Quick Installation + +The Emacs lisp source for Ledger-mode is included with the source +distribution of Ledger. It is entirely included in the @file{lisp} +subdirectory. To use ledger mode include the following in your Emacs +initialization file (@file{~/.emacs}, @file{~/.emacs.d/init.el}, +@file{~/.Aquamacs/Preferences.el} + +@smallexample +(add-to-list 'load-path (expand-file-name "/path/to/ledger/source/lisp/")) +(load "ldg-new") +(add-to-list 'auto-mode-alist '("\\.ledger$" . ledger-mode)) +@end smallexample + +This sets up Emacs to automatically recognize files that end with +@file{.ledger} and start Ledger mode. Nothing else should be required +as long as the ledger command line utility is properly installed. + +@node Menus, Quick Demo, Quick Installation, Introduction to Ledger Mode +@section Menus + +The vast majority of Ledger-mode functionality is available from the +Emacs menu system. The keystrokes are shown in the menu to help you +learn the faster keyboard methods. + +@node Quick Demo, , Menus, Introduction to Ledger Mode +@section Quick Demo + +Load the demo file @file{demo.ledger} from the Ledger source +@file{test/input} directory. The ledger will be loaded and font +highlighted. At this point you could manually edit transactions and run +Ledger from a convenient command line. + +@menu +* Quick Add:: +* Reconciliation:: +* Reports:: +* Narrowing:: +@end menu + +@node Quick Add, Reconciliation, Quick Demo, Quick Demo +@subsection Quick Add + +As simple as the ledger transaction format is, it can still be daunting +to add many transactions manually. Ledger provides two way to add +transactions with minimal typing. Both are based on the idea that most +transactions are repetitions of earlier transactions. + +In the @file{demo.ledger} buffer enter a date using the correct +format. Then type the first few characters of another payee in the +@file{demo.ledger} buffer. Type @code{C-c TAB}. Ledger-mode will +search for a Payee that has the same beginning and copy the rest of the +transaction to you new entry. + +Additionally you can use the ledger xact command, by typing @code{C-c +C-a} then typing a close match to the payee. Ledger mode will call +@code{ledger xact} with the data you enter and place the transaction in +the proper chronological place in the ledger. + +@node Reconciliation, Reports, Quick Add, Quick Demo +@subsection Reconciliation + +The biggest task of maintaining a ledger is ensuring the it matches the +outside world. This process is called reconciliation (@pxref{Basics of +Reconciliation}) and can be quite onerous. Ledger mode attempts to make +it as painless as possible. + +In the @file{demo.ledger} buffer type @code{C-c C-r}. Emacs will prompt +for an account to reconcile in the mini-buffer. Enter @code{Checking}. +Emacs will then prompt for a target value. The target value is the +amount you want the cleared transactions in the buffer to total. +Normally this would be the ending value from your bank statement, or the +latest value in your on-line transaction summary. Enter @code{1710}. +Note that Ledger-mode assumes your are using $ (USD) as your default +commodity, this can be easily changed in the customization +variables. @xref{Ledger-mode Customization}. + +You now see a list of uncleared transactions in a buffer below the +@file{demo.ledger} buffer. Touching the space bar will mark a +transaction as pending and display the current cleared (and pending) +balance, along with the difference remaining to meet your target. Clear +the first three transactions, and you will see the difference to target +reach $0. End the reconciliation by typing @code{C-c C-c}. This saves +the demo.ledger buffer and marks the transactions and finally cleared. +Type @code{q} to close out the reconciliation buffer. + +@node Reports, Narrowing, Reconciliation, Quick Demo +@subsection Reports + +The real power of Ledger is in it reporting capabilities. Reports can +be run and displayed in a separate Emacs buffer. In the +@file{demo.ledger} buffer, type @code{C-c C-o C-r}. In the mini-buffer +Emacs will prompt for a report name. There are a few built-in reports, +and you can add any report you need @xref{Adding and Editing Reports}. + +In the mini-buffer type @code{account}. When prompted for an account +type @code{checking}. In another buffer you will see a Ledger register +report. You can move around the buffer, with the point on a transaction, +type @code{C-c C-c}. Ledger mode will take you directly to that +transaction in the @file{demo.ledger} buffer. + +Another built-in report is the balance report. In the +@file{demo.ledger} buffer, type @code{C-c C-o C-r}. When prompted for a +report to run, type @code{bal}, and a balance report of all accounts +will be shown. + +@node Narrowing, , Reports, Quick Demo +@subsection Narrowing + +A ledger file can get very large. It can be helpful to collapse the buffer +to display only the transactions you are interested in. Ledger-mode +copies the @code{occur} mode functionality. Typing @code{C-c C-f} and +entering any regex in the mini-buffer will show only transactions that +match the regex. The regex can be on any field, or amount. + +@node The Ledger Buffer, The Reconcile Buffer, Introduction to Ledger Mode, Top +@chapter The Ledger Buffer +@menu +* Adding Transactions:: +* Editing Amounts:: +* Marking Transactions:: +* Deleting Transactions:: +* Sorting Transactions:: +* Narrowing Transactions:: +@end menu + +@node Adding Transactions, Editing Amounts, The Ledger Buffer, The Ledger Buffer +@section Adding Transactions + +Beyond the two ways of quickly adding transactions (@pxref{Quick Add}) +Ledger-mode assists you by providing robust @code{TAB} completion for +payees and accounts. Ledger-mode will scan the existing buffer for +payees and accounts. Included files are not currently included in the +completion scan. Repeatedly hitting @code{TAB} will cycle through the +possible completions. + +Ledger mode can also help you keep your amounts in alignment. Setting +@code{ledger-post-auto-adjust-amounts} to true tells Ledger-mode to +automatically place any amounts such that their last digit is aligned to +the column specified by @code{ledger-post-amount-alignment-column}, +which defaults to 52. @xref{Ledger Post Customization Group}. + +@node Quick Balance Display +@subsection Quick Balance Display +You will often want to quickly check the balance of an account. The +easiest way it to position point on the account you are interested in, +and type @code{C-C C-P}. The minibuffer will ask you to verify the name +of the account you want, if it is already correct hit return, then the +balance of the account will be displayed in the minibuffer. + +@node Editing Amounts, Marking Transactions, Adding Transactions, The Ledger Buffer +@section Editing Amounts +GNU Calc is a very powerful Reverse Polish Notation calculator built +into all recent version of Emacs. Ledger-mode makes it easy to +calculate values for amount by integrating GNU Calc. With the point +anywhere in the same line as a posting, typing @code{C-c C-b} will +bring up the Calc buffer, and push the current amount for the posting +onto the top of the Calc stack. Perform any calculations you need to +arrive at the final value, then type @code{y} to yank the value at the +top of stack back into the ledger buffer. Note: GNU Calc does not +directly support commas as decimal separators. Ledger mode will +translate values from decimal-comma format to decimal-period format for +use in Calc, but it cannot intercept the value being yanked form the +Calc stack, so decimal-comma users will have to manually replace the +period with a comma. + + +@node Marking Transactions, Deleting Transactions, Editing Amounts, The Ledger Buffer +@section Marking Transactions +Ledger considers transaction or posting to be in one of three states: +uncleared, cleared, and pending. For calculation Ledger ignores these +states unless specifically instructed to use them. Ledger-mode assigns +some additional meaning to the states: +@itemize +@item Uncleared. +No state. This is equivalent to sticking a check in the mail. It has +been obligated, but not been cashed by the recipient. It could also +apply to credit/debit card transactions that have not been cleared into +your account balance. You bank may call these transactions 'pending', +but Ledger-mode uses a slightly different meaning. +@item Pending. +Ledger-mode's reconciliation function see pending transactions as an +intermediate step in reconciling an account. When doing a +reconciliation (@pxref{Reconciliation}), marking a transaction as +pending means that you have seen the transaction finally recorded by the +recipient, but you have not completely reconciled the account. +@item Cleared. +The transaction has been completely recognized by all parties to the transaction. +@end itemize + +Clearing complete transactions is done by typing @code{C-c C-e} with +point in a transaction. This places an asterisk (@code{*}) after the +date. Clearing individual postings is done by typing @code{C-c C-c} +while in a posting. This places an asterisk prior to the posting. + +@node Deleting Transactions, Sorting Transactions, Marking Transactions, The Ledger Buffer +@section Deleting Transactions +Along with normal buffer editing methods to delete text, Ledger mode +provides an easy way to delete the transaction under point: @code{C-c +C-d}. The advantage to using this method is that the complete +transaction operation is in the undo buffer. + +@node Sorting Transactions, Narrowing Transactions, Deleting Transactions, The Ledger Buffer +@section Sorting Transactions + +As you operating on the Ledger files, they may become disorganized. For +the most part, Ledger doesn't care, but our human brains prefer a bit of +order. Sorting the transactions in a buffer into chronological order +can help bring order to chaos. Ledger sort (@code{C-c C-s}) will sort +all of the transactions in a region by date. Ledger-mode isn't +particularly smart about handling dates and it simply sorts the +transactions using the string at the beginning of the transaction. So, +you should use the preferred ISO 8601 standard date format @code{YYYY/MM/DD} +which easily sorts. + +Note, there is a menu entry to sort the entire buffer. Special +transactions like automated transaction, will be moved in the sorting +process and may not function correctly afterwards. For this reason +there is no key sequence. + +You can limit the allowed sort region by using embedded Ledger-mode +markup within your ledger. For exmaple +@smallexample +<<< information to not sort >>> + +; Ledger-mode: Start sort + +<<< xacts to sort >>> + +; Ledger-mode: End sort + +<<< information to not sort >>> +@end smallexample +You can use menu entries to insert start and end markers. These +functions will automatically delete old markers and put new new marker +at point. + +@node Narrowing Transactions, , Sorting Transactions, The Ledger Buffer +@section Narrowing Transactions + +Often you will want to run Ledger register reports just to look at a +specific set of transactions. If you don't need the running total +calculation handled by Ledger, Ledger-mode provides a rapid way of +narrowing what is displayed in the buffer in a way that is simpler than +the Ledger register command. + +Based on the Emacs Occur mode by Alexey Veretennikov, Ledger-occur hides +all transactions that do NOT meet a specific regular expression. The +regular expression can match on any part of the transaction. If you +want to find all transactions whose amount ends in .37, you can do that +( I don't know why, but hey, whatever ever floats you aerostat). + +Using @code{C-c C-f} or the @code{Hide Xacts} menu entry, enter a +regular expression in the minibuffer. Ledger-mode will hide all other +transactions. For details of the regular expression syntax, see +@ref{(emacs)Regexps, Syntax of Regular Expressions} or +@ref{(elisp)Regular Expressions, Regular Expressions}. A few examples +using the @file{demo.ledger} are given here: + +@table @samp +@item Groceries +Show only transactions that have a posting to the `Groceries' account. +@item ^2011/01 +Show only transactions occurring in January of 2011. +@item ^2011/.*/25 +Show only transactions occurring on the 25th of the month in 2011 +@item auto +Show only transactions with payees or accounts or comments containing `auto' +@item harley$ +Show only transcations with any line ending with `harley' +@end table + +To show back all transactions simply invoke @code{Hide Xacts} or @code{C-c +C-f} again. + +@node The Reconcile Buffer, The Report Buffer, The Ledger Buffer, Top +@chapter The Reconcile Buffer +@menu +* Basics of Reconciliation:: +* Starting a Reconciliation:: +* Mark Transactions Pending:: +* Edit Transactions During Reconciliation:: +* Finalize Reconciliation:: +* Adding and Deleting Transactions during Reconciliation:: +* Changing Reconciliation Account:: +* Changing Reconciliation Target:: +@end menu + +@node Basics of Reconciliation, Starting a Reconciliation, The Reconcile Buffer, The Reconcile Buffer +@section Basics of Reconciliation + +Even in this relatively modern era, financial transactions do not happen +instantaneously, unless you are paying cash. When you swipe your debit +card the money may take several days to actually come out of your +account, or a check may take several days to ``clear''. That is the +root of the difference between ``obligating'' funds and ``expending'' +funds. Obligation says you have agreed to pay it, the expenditure +doesn't happen until the money actually leaves your account. Or in the +case of receiving payment, you have an account receivable until the +money has actually made it to you. + +After an account has been reconciled you have verified that all the +transactions in that account have been correctly recorded and all +parties agree. + +@node Starting a Reconciliation, Mark Transactions Pending, Basics of Reconciliation, The Reconcile Buffer +@section Starting a Reconciliation + +To start reconciling an account you must have a target, both the +transactions that you know about and the transactions the bank knows +about. You can get this from a monthly statement, or from checking your +online transaction history. It also helps immensely to know the final +cleared balance you are aiming for. + +Use menu @code{Reconcile Account} or @code{C-c C-r} and enter the +account you wish to reconcile in the mini-buffer. Ledger-mode is not +particular about what you enter for the account. You can leave it blank +and Reconcile Mode will show you ALL uncleared transactions. After you +enter the account enter the target amount. Ledger expects you to enter +an amount with a commodity. It assumes initially that you are using $ +(USD) as your default commodity. If you are working in a different +currency you can change the default in variable +@code{ledger-reconcile-default-commodity} to whatever you need. If you +work in multiple commodities simply enter the commoditized amount (for +example @code{340 VSDX}, for 340 shares of VSDX). + +Ledger-mode reconcile cannot currently reconcile accounts that have +multiple commodities, such as brokerage accounts. You may use +reconciliation mode to clear transactions, but balance calculations will +not display the complete list of commodities. + +@node Mark Transactions Pending, Edit Transactions During Reconciliation, Starting a Reconciliation, The Reconcile Buffer +@section Mark Transactions Pending + +The @file{*Reconcile*} buffer will show all the uncleared transactions +that meet the criteria set in the regex. By default uncleared +transactions are shown in red. When you have verified that a +transaction has been correctly and completely recorded by the opposing +party, mark the transaction as pending using the space bar. Continue +this process until you agree with the opposing party and the difference +from your target is zero. + +@node Edit Transactions During Reconciliation, Finalize Reconciliation, Mark Transactions Pending, The Reconcile Buffer +@section Edit Transactions during Reconciliation + +If you find errors during reconciliation. You can visit the transaction +under point in the @file{*Reconcile*} buffer by hitting the @code{enter} +key. This will take you to the transaction in the Ledger buffer. When +you have finished editing the transaction saving the buffer will +automatically return you to the @file{*Reconcile*} buffer and you can +mark the transaction if appropriate. + +@node Finalize Reconciliation, Adding and Deleting Transactions during Reconciliation, Edit Transactions During Reconciliation, The Reconcile Buffer +@section Finalize Reconciliation + +Once you have marked all transactions as pending and the cleared balance +is correct. Finish the reconciliation by typing @code{C-c C-c}. This +marks all pending transaction as cleared and saves the ledger buffer. + +@node Adding and Deleting Transactions during Reconciliation, Changing Reconciliation Account, Finalize Reconciliation, The Reconcile Buffer +@section Adding and Deleting Transactions during Reconciliation + +While reconciling, you may find new transactions that need to be entered +into your ledger. Simply type @code{a} to bring up the quick add for +the ledger buffer. + +Typing @code{d} will delete the transaction under point in the +@file{*Reconcile*} buffer from the ledger buffer. + +@node Changing Reconciliation Account, Changing Reconciliation Target, Adding and Deleting Transactions during Reconciliation, The Reconcile Buffer +@section Changing Reconciliation Account + +You can conveniently switch the account being reconciled by typing +@code{g}, and entering a new account to reconcile. This simply restarts +the reconcile process. Any transactions that were marked `pending' in +the ledger buffer are left in that state when the account is switched. + +@node Changing Reconciliation Target, , Changing Reconciliation Account, The Reconcile Buffer +@section Changing Reconciliation Target + +If for some reason during reconciliation your target amount changes, +type @code{t} and enter the new target value. + + +@node The Report Buffer, Customizing Ledger-mode, The Reconcile Buffer, Top +@chapter The Report Buffer +@menu +* Running Basic Reports:: +* Adding and Editing Reports:: +* Reversing Report Order:: +@end menu + +@node Running Basic Reports, Adding and Editing Reports, The Report Buffer, The Report Buffer +@section Running Reports +The real power behind Ledger is in its amazing reporting capability. +Ledger-mode provides easy facility to run reports directly from Emacs. +It has four reports built-in and facilities for adding custom reports. + +Typing @code{C-c C-o C-r} or using menu @code{Ledger Run Report} prompt +for the name of a saved report. The built-in reports are: +@table @samp +@item bal +Produce a balance reports of all accounts. +@item reg +Produce a register report of all transactions. +@item payee +Prompt for a payee, the produce a register report of all transaction +involving that payee. +@item account +Prompt for an account, the produce a register report of all transaction +involving that account. +@end table + + +@node Adding and Editing Reports, Reversing Report Order, Running Basic Reports, The Report Buffer +@section Adding and Editing Reports +@menu +* Expansion Formats:: +* Make Report Transactions Active:: +@end menu + +If you type a report name that Ledger-mode doesn't recognize it will +prompt you for a ledger command line to run. That command is +automatically saved with the name given and you can re-run it at any +time. + +There are two ways to edit the command line for a report. The first is +to provide a prefix argument to the run-report command. For example, +type @code{M-1 C-c C-o C-r}. This will prompt you for the report name, +then present the report command line to be edited. When you hit enter, +the report will be run, but it will not be permanently saved. If you +want to save it, type @code{S} in the @file{*Ledger Report*} buffer you +will have the option to give it a new name, or overwrite the old report. + +Deleting reports is accomplished by typing @code{C-c C-o C-e} Edit Reports +in the ledger buffer, or typing @code{e} in the @file{*Ledger Report*} +buffer. This takes you to the Emacs customization window for the +@code{ledger-reports} variable. Use the widgets to delete the report +you want removed. + +@node Expansion Formats, Make Report Transactions Active, Adding and Editing Reports, Adding and Editing Reports +@subsection Expansion Formats + +It is sometime convenient to leave room to customize a report without +saving the command line every time. For example running a register +report for a specific account, enter at runtime by the user. The +built-in report @file{account} does exactly that, using a variable +expansion to prompt the user for the account to use. There are four +variable that can be expanded to run a report: + +@table @samp +@item ledger-file +Returns the file to be operated on. +@item payee +Prompts for a payee. +@item account +Prompt for an account. +@item value +Prompt for a tag value. +@end table + +You can use these expansion values in your ledger report commands. For +example, if you wanted to specify a register report the displayed +transactions from a user-determined account with a particular meta-data +tag value, you specify the following command line: +@smallexample +ledger -f %(ledger-file) reg %(account) --limit \"tag('my-tag') =~/%(value)/\" +@end smallexample + +@noindent Note how the double-quotes are escaped with back-slashes. + +@node Make Report Transactions Active, , Expansion Formats, Adding and Editing Reports +@subsection Make Report Transactions Active + +In a large register report it is convenient to be able to jump to the +source transaction. Ledger-mode will automatically include source +information in every register file that doesn't contain a +@code{--subtotal} option. It does this by adding a +@code{--prepend-format='%(filename):%(beg_line):'} to the register +report command-line you specify. You should never have to see this, but +if there is an error in your ledger output this additional information +may not get stripped out of the visible report. + +@node Reversing Report Order, , Adding and Editing Reports, The Report Buffer +@section Reversing Report Order + +Often, banks show their online transaction histories with the most recent +transaction at the top. Ledger itself cannot do a sensible ledger +report in reverse chronological order, if you sort on reverse date the +calculation will also run in the opposite direction. If you want to +compare a ledger register report to a bank report with the most recent +transactions at the top, type R in the @file{*Ledger Report*} buffer and +it will reverse the order of the transactions and maintain the proper +mathematical sense. + + +@node Customizing Ledger-mode, Generating Ledger Regression Tests, The Report Buffer, Top +@chapter Customizing Ledger-mode +@menu +* Ledger-mode Customization:: +* Customization Variables:: +@end menu + +@node Ledger-mode Customization, Customization Variables, Customizing Ledger-mode, Customizing Ledger-mode +@section Ledger-mode Customization + +Ledger-mode has several options available for configuration. All +options can be configured through the Emacs customization menus, or +specified in your Emacs initialization file. The complete list of +options is shown below. To change the option using the Emacs +customization menu, simply chose customize in the Options menu and look +for Ledger under the data options. Alternately you can choose +``Customize Specific Group'' and enter ``Ledger'' as the group. + +@node Customization Variables, , Ledger-mode Customization, Customizing Ledger-mode +@section Customization Variables + +@menu +* Ledger Customization Group:: +* Ledger Reconcile Customization Group:: +* Ledger Report Customization Group:: +* Ledger Faces Customization Group:: +* Ledger Post Customization Group:: +* Ledger Exec Customization Group:: +* Ledger Test Customization Group:: +* Ledger Texi Customization Group:: +@end menu + +@node Ledger Customization Group, Ledger Reconcile Customization Group, Customization Variables, Customization Variables +@subsection Ledger Customization Group +@table @code +@item ledger-occur-use-face-shown + If non-nil, use a custom face for xacts shown in `ledger-occur' mode using @code{ledger-occur-xact-face}. +@item ledger-clear-whole-transactions + If non-nil, clear whole transactions, not individual postings. +@item ledger-highlight-xact-under-point + If non-nil, highlight xact under point using @code{ledger-font-highlight-face}. +@end table + +@node Ledger Reconcile Customization Group, Ledger Report Customization Group, Ledger Customization Group, Customization Variables +@subsection Ledger Reconcile Customization Group + +@table @code +@item ledger-reconcile-default-commodity +The default commodity for use in target calculations in ledger +reconcile. Defaults to $ (USD) +@item ledger-recon-buffer-name + Name to use for reconciliation window. +@item ledger-narrow-on-reconcile + If non-nil, limit transactions shown in main buffer to those matching the + reconcile regex. +@item ledger-buffer-tracks-reconcile-buffer + If non-nil, then when the cursor is moved to a new xact in the recon + window. +@item ledger-reconcile-force-window-bottom + If non-nil, make the reconcile window appear along the bottom of the + register window and resize. +@item ledger-reconcile-toggle-to-pending + If non-nil, then toggle between uncleared and pending (@code{!}). If false + toggle between uncleared and cleared (@code{*}) +@end table + +@node Ledger Report Customization Group, Ledger Faces Customization Group, Ledger Reconcile Customization Group, Customization Variables +@subsection Ledger Report Customization Group + +@table @code +@item ledger-reports + Definition of reports to run. +@item ledger-report-format-specifiers + An alist mapping ledger report format specifiers to implementing functions. +@end table + + +@node Ledger Faces Customization Group, Ledger Post Customization Group, Ledger Report Customization Group, Customization Variables +@subsection Ledger Faces Customization Group +Ledger Faces : Ledger mode highlighting +@table @code +@item ledger-font-uncleared-face +Default face for Ledger +@item ledger-font-cleared-face +Default face for cleared (*) transactions +@item ledger-font-highlight-face +Default face for transaction under point +@item ledger-font-pending-face +Default face for pending (!) transactions +@item ledger-font-other-face +Default face for other transactions +@item ledger-font-posting-account-face +Face for Ledger accounts +@item ledger-font-posting-account-cleared-face +Face for cleared Ledger accounts +@item ledger-font-posting-account-pending-face +Face for Ledger pending accounts + +@item ledger-font-posting-amount-face +Face for Ledger amounts +@item ledger-occur-narrowed-face +Default face for Ledger occur mode hidden transactions +@item ledger-occur-xact-face +Default face for Ledger occur mode shown transactions +@item ledger-font-comment-face +Face for Ledger comments +@item ledger-font-reconciler-uncleared-face +Default face for uncleared transactions in the reconcile window +@item ledger-font-reconciler-cleared-face +Default face for cleared (*) transactions in the reconcile window +@item ledger-font-reconciler-pending-face +Default face for pending (!) transactions in the reconcile window +@item ledger-font-report-clickable-face +Default face for pending (!) transactions in the reconcile window +@end table + +@node Ledger Post Customization Group, Ledger Exec Customization Group, Ledger Faces Customization Group, Customization Variables +@subsection Ledger Post Customization Group +Ledger Post : +@table @code + +@item ledger-post-auto-adjust-amounts +If non-nil, then automatically align amounts to column specified in +@code{ledger-post-amount-alignment-column} +@item ledger-post-amount-alignment-column +The column Ledger-mode uses to align amounts +@item ledger-default-acct-transaction-indent +Default indentation for account transactions in an entry. +@item ledger-post-use-completion-engine +Which completion engine to use, iswitchb, ido, or built-in +@item ledger-post-use-ido +@end table + +@node Ledger Exec Customization Group, Ledger Test Customization Group, Ledger Post Customization Group, Customization Variables +@subsection Ledger Exec Customization Group + +Ledger Exec : Interface to the Ledger command-line accounting program. + +@table @code +@item ledger-binary-path +Path to the ledger executable. +@item ledger-init-file-name +Location of the ledger initialization file. nil if you don't have one +@end table + + +@node Ledger Test Customization Group, Ledger Texi Customization Group, Ledger Exec Customization Group, Customization Variables +@subsection Ledger Test Customization Group +@table @code +@item ledger-source-directory + Directory where the Ledger sources are located. +@item ledger-test-binary + Directory where the debug binary. +@end table + +@node Ledger Texi Customization Group, , Ledger Test Customization Group, Customization Variables +@subsection Ledger Texi Customization Group + +@table @code +@item ledger-texi-sample-doc-path +Location for sample data to be used in texi tests, defaults to @file{~/ledger/doc/sample.dat} +@item ledger-texi-normalization-args +texi normalization for producing ledger output, defaults to ``@code{--args-only --columns 80}'' +@end table + +@node Generating Ledger Regression Tests, Embedding Example results in Ledger Documentation, Customizing Ledger-mode, Top +@chapter Generating Ledger Regression Tests + +Work in Progress. + +@node Embedding Example results in Ledger Documentation, Hacking Ledger-mode, Generating Ledger Regression Tests, Top +@chapter Embedding Example results in Ledger Documentation + +Work in Progress. + +@node Hacking Ledger-mode, , Embedding Example results in Ledger Documentation, Top +@chapter Hacking Ledger-mode + +Work in Progress. +@bye diff --git a/doc/ledger.1 b/doc/ledger.1 index b96e4a7d..659d3fbb 100644 --- a/doc/ledger.1 +++ b/doc/ledger.1 @@ -92,29 +92,23 @@ Report of postings matching the .Ar report-query in CSV format (comma-separated values). Useful for exporting data to a spreadsheet for further analysis or charting. -.It Nm draft Oo Ar draft-template Oc +.It Nm entry Oo Ar entry-template Oc Generate and display a new, properly formatted Ledger transaction by comparing the -.Ar draft-template +.Ar entry-template to the transactions in your data file(s). For more information on draft templates and using this command to quickly create new transactions, see the section -.Sx DRAFTS . +.Sx ENTRIES . .Pp -The synonyms -.Nm entry -and +The synonym .Nm xact -are also accepted. +is also accepted. .It Nm emacs Oo Ar query Oc Outputs posting and transaction data in a format readily consumed by the Emacs editor, in a series of Lisp forms. This is used by the .Li ledger.el Emacs mode to process reporting data from Ledger. -.Pp -The synonym -.Nm lisp -is also accepted. .It Nm equity Oo Ar report-query Oc Prints a series of transactions that balance current totals for accounts matching the @@ -586,9 +580,9 @@ for example: .It Nm xact .El .Pp -.Sh DRAFTS +.Sh ENTRIES .Pp -.Sh FORMATS +.Sh FORMATS .Pp .Sh DEBUG COMMANDS In addition to the regular reporting commands, Ledger also accepts several diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 30d7d5a4..3f099d93 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -75,10 +75,11 @@ twinkling in their father's CRT. * Reporting Commands:: * Command-line Syntax:: * Budgeting and Forecasting:: +* Time Keeping:: * Value Expressions:: * Format Strings:: -* Ledger for Developers:: * Extending with Python:: +* Ledger for Developers:: * Major Changes from version 2.6:: * Example Data File:: * Miscellaneous Notes:: @@ -235,8 +236,8 @@ $ ledger -f ledger.dat register Bell @end smallexample An important difference between Ledger and other finance packages is -that journal will never alter your input file. You can create and edit -that file in any way you prefer, but journal is only for analyzing the +that Ledger will never alter your input file. You can create and edit +that file in any way you prefer, but Ledger is only for analyzing the data, not for altering it. @@ -261,7 +262,7 @@ enter these commands: Ledger has a complete online help system based on GNU Info. This manual can be searched directly from the command line using the following options: -@option{ledger --help} bring up this entire manual in your tty. +@code{ledger --help} bring up this entire manual in your tty. If you need help on how to use Ledger, or run into problems, you can join the Ledger mailing list at the following Web address: @@ -270,8 +271,8 @@ join the Ledger mailing list at the following Web address: http://groups.google.com/group/ledger-cli @end smallexample -@noindent You can also find help at the @samp{#ledger} channel on the IRC server -@samp{irc.freenode.net}. +@noindent You can also find help at the @code{#ledger} channel on the IRC server +@code{irc.freenode.net}. @cindex tutorial @node Ledger Tutorial , Principles of Accounting, Introduction to Ledger, Top @@ -280,7 +281,6 @@ http://groups.google.com/group/ledger-cli @menu * Start a Journal:: * Run Some Reports:: -* Command Line Quick Reference:: @end menu @node Start a Journal, Run Some Reports, Ledger Tutorial , Ledger Tutorial @@ -293,10 +293,9 @@ called @file{drewr3.dat} (@pxref{Example Data File}). Copy it someplace convenient and open up a terminal window in that directory. -If you would rather start with your own journal right away please skip -to @xref{Keeping a Journal}. +If you would rather start with your own journal right away please see @ref{Keeping a Journal}. -@node Run Some Reports, Command Line Quick Reference, Start a Journal, Ledger Tutorial +@node Run Some Reports, , Start a Journal, Ledger Tutorial @section Run a Few Reports @menu @@ -306,6 +305,16 @@ to @xref{Keeping a Journal}. * Using the Windows command line:: @end menu +Please note that as a command line program, Ledger is controlled from +your shell. There are several different command shells that all behave +slightly differently with repsect to some special characters. In +particular, the BaSH shell will interpret $ signs differently than +ledger and they must be escaped to reach the actual program. Another +example is zsh, which will interpret ^ differently than ledger expects. +In all cases that follow you should take that into account when entering +the commandline arguments given. There are too many variations between +shells to give concrete examples for each. + @node Balance Report, Register Report, Run Some Reports, Run Some Reports @subsection Balance Report @cindex balance report @@ -370,11 +379,11 @@ To show all transactions and a running total: ledger -f drewr3.dat register @end smallexample -Ledger will generate: +@noindent Ledger will generate: @smallexample 10-Dec-01 Checking balance Assets:Checking $ 1,000.00 $ 1,000.00 - Equity:Opening Balances $ -1,000.00 0 + Equity:Opening Balances $ -1,000.00 0 10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50 Expense:Food:Groceries $ 37.50 $ 75.00 Expense:Food:Groceries $ 37.50 $ 112.50 @@ -421,14 +430,14 @@ $ ledger -f drewr3.dat register Groceries 11-Jan-19 Grocery Store Expense:Food:Groceries $ 44.00 $ 334.00 @end smallexample -@noindent Which matches the balance reported for the @samp{Groceries} account: +@noindent Which matches the balance reported for the @code{Groceries} account: @smallexample $ ledger -f drewr3.dat balance Groceries $ 334.00 Expenses:Food:Groceries @end smallexample -@noindent If you would like to find transaction to only a certain payee use @samp{payee} or @@: +@noindent If you would like to find transaction to only a certain payee use @code{payee} or @@: @smallexample $ ledger -f drewr3.dat register payee "Organic" 10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50 @@ -447,7 +456,8 @@ $ ledger -f drewr3.dat register payee "Organic" A very useful report is to show what your obligations are versus what expenditures have actually been recorded. It can take several days for a check to clear, but you should treat it as money spent. The -@samp{cleared} report shows just that: +@code{cleared} report shows just that (note that the cleared report will +not format correctly for accounts that contain multiple commodities): @smallexample $ ledger -f drewr3.dat cleared @@ -473,7 +483,7 @@ $ ledger -f drewr3.dat cleared $ -243.60 0 @end smallexample -@noindent The first column shows the outstanding balance, the second column show the ``cleared'' balance. +@noindent The first column shows the outstanding balance, the second column shows the ``cleared'' balance. @node Using the Windows command line, , Cleared Report, Run Some Reports @subsection Using the Windows Command Line @cindex windows cmd.exe @@ -482,149 +492,6 @@ Using ledger under the windows command shell has one significant limitation. CMD.exe is limited to standard ASCII characters and as such cannot display any currency symbols other than dollar signs ($). -@node Command Line Quick Reference, , Run Some Reports, Ledger Tutorial -@section Command Line Quick Reference - -@menu -* Reporting Commands Quick Reference:: -* Basic Options Quick Reference:: -* Report Filtering Quick Reference:: -* Error Checking and Calculation Options:: -* Output Customization Quick Reference:: -* Grouping Options:: -* Commodity Reporting Quick Reference:: -@end menu - -@node Reporting Commands Quick Reference, Basic Options Quick Reference, Command Line Quick Reference, Command Line Quick Reference -@subsection Reporting Commands -@multitable @columnfractions .2 .8 -@item @strong{Report} @tab @strong{Description} -@item @code{balance} @tab Show account balances -@item @code{register} @tab Show all transactions with running total -@item @code{csv} @tab Show transactions in csv format, for exporting to other programs -@item @code{print} @tab Print transaction in a ledger readable format -@item @code{output} @tab Similar to print without included transactions -@item @code{xml} @tab Produce XML output of the register command -@item @code{emacs} @tab Produce Emacs lisp output -@item @code{equity} @tab Print account balances as transactions -@item @code{prices} @tab Print price history for matching commodities -@item @code{pricedb} @tab Print price history for matching commodities in ledger readable format -@item @code{xact} @tab Used to generate transactions based on previous postings -@end multitable - -@node Basic Options Quick Reference, Report Filtering Quick Reference, Reporting Commands Quick Reference, Command Line Quick Reference -@subsection Basic Options -@multitable @columnfractions .1 .25 .65 -@item @strong{Short} @tab @strong{Long} @tab @strong{Description} -@item @code{-h} @tab @code{--help} @tab prints summary of all options -@item @code{-v} @tab @code{--version} @tab prints version of ledger executable -@item @code{-f FILE} @tab @code{--file FILE} @tab read @file{FILE} as a ledger file -@item @code{-o FILE} @tab @code{--output FILE} @tab redirects output to @file{FILE} -@item @code{-i FILE} @tab @code{--init-file FILE} @tab specify options file -@item @code{-a NAME} @tab @code{--account NAME} @tab specify default account name for QIF file postings -@end multitable - -@node Report Filtering Quick Reference, Error Checking and Calculation Options, Basic Options Quick Reference, Command Line Quick Reference -@subsection Report Filtering -@multitable @columnfractions .1 .25 .65 -@item @strong{Short} @tab @strong{Long} @tab @strong{Description} -@item @code{-c} @tab @code{--current} @tab Display transaction on or before the current date -@item @code{-b DATE} @tab @code{--begin DATE} @tab Begin reports on or after @code{DATE} -@item @code{-e DATE} @tab @code{--end DATE} @tab Limits end date of transactions for report -@item @code{-p STR} @tab @code{--period} @tab Set report period to STR -@item @code{ } @tab @code{--period-sort} @tab Sort postings within each period -@item @code{-C} @tab @code{--cleared} @tab Display only cleared postings -@item @code{} @tab @code{--dc} @tab Display register or balance in debit/credit format -@item @code{-U} @tab @code{--uncleared} @tab Display only uncleared postings -@item @code{-R} @tab @code{--real} @tab Display only real postings -@item @code{-L} @tab @code{--actual} @tab Displays only actual postings, not automated -@item @code{-r} @tab @code{--related} @tab Display related postings -@item @code{} @tab @code{--budget} @tab Display how close your postings meet your budget -@item @code{} @tab @code{--add-budget} @tab Shows un-budgeted postings -@item @code{} @tab @code{--unbudgeted} @tab Shows only un-budgeted postings -@item @code{} @tab @code{--forecast} @tab Project balances into the future -@item @code{-l EXPR} @tab @code{--limit EXPR} @tab Limits postings in calculations -@item @code{-t EXPR} @tab @code{--amount} @tab Change value expression reported in register report -@item @code{-T EXPR} @tab @code{--total} @tab Change the value expression used for ``totals'' column in register and balance reports -@end multitable - -@node Error Checking and Calculation Options, Output Customization Quick Reference, Report Filtering Quick Reference, Command Line Quick Reference -@subsection Error Checking and Calculation Options - -@multitable @columnfractions .1 .25 .65 -@item @strong{Short} @tab @strong{Long} @tab @strong{Description} -@item @code{} @tab @code{--strict} @tab accounts, tags or commodities not previously declared will cause warnings -@item @code{} @tab @code{--pedantic} @tab accounts, tags or commodities not previously declared will cause errors -@item @code{} @tab @code{--check-payees} @tab enable strict and pedantic checking for payees as well as accounts, commodities and tags. -@item @code{} @tab @code{--immediate} @tab instructs ledger to evaluate calculations immediately rather than lazily -@end multitable - - -@node Output Customization Quick Reference, Grouping Options, Error Checking and Calculation Options, Command Line Quick Reference -@subsection Output Customization -@multitable @columnfractions .15 .4 .45 -@item @strong{Short} @tab @strong{Long} @tab @strong{Description} -@item @code{-n} @tab @code{--collapse} @tab Collapse transactions with multiple postings -@item @code{-s} @tab @code{--subtotal} @tab Report register as a single subtotal -@item @code{-P} @tab @code{--by-payee} @tab Report subtotals by payee -@item @code{-E} @tab @code{--empty} @tab Include empty accounts in report -@item @code{-W} @tab @code{--weekly} @tab Report posting totals by week -@item @code{-Y} @tab @code{--yearly} @tab Report posting totals by year -@item @code{} @tab @code{--dow} @tab report Posting totals by day of week -@item @code{-S EXPR} @tab @code{--sort EXPR} @tab Sorts a report using @code{EXPR} -@item @code{-w} @tab @code{--wide} @tab Assume 132 columns instead of 80 -@item @code{} @tab @code{--head N} @tab Report the first N postings -@item @code{} @tab @code{--tail N} @tab Report the last N postings -@item @code{} @tab @code{--pager prog} @tab Direct output @code{prog} pager program -@item @code{-A} @tab @code{--average} @tab Reports average posting value -@item @code{-D} @tab @code{--deviation} @tab Reports each posting deviation from the average -@item @code{-%} @tab @code{--percent} @tab Show subtotals in the balance report as percentages -@c @item @code{} @tab @code{--totals} @tab Include running total in the @code{xml} report -@item @code{} @tab @code{--pivot TAG} @tab produce a pivot table of the tag type specified -@item @code{-j} @tab @code{--amount-data} @tab Show only date and value column -@item @code{-J} @tab @code{--total-data} @tab Show only dates and totals -@item @code{-d EXPR} @tab @code{--display EXPR} @tab Limit only the display of certain postings -@item @code{-y STR} @tab @code{--date-format STR} @tab Change the basic date format used in reports -@item @code{-F STR} @tab @code{--format STR} @tab Set reporting format -@item @code{} @tab @code{--balance-format STR} @tab -@item @code{} @tab @code{--register-format STR} @tab -@item @code{} @tab @code{--print-format STR} @tab -@item @code{-j register} @tab @code{--plot-amount-format STR} @tab -@item @code{-J register} @tab @code{--plot-total-format STR} @tab -@item @code{} @tab @code{--equity-format STR} @tab -@item @code{} @tab @code{--prices-format STR} @tab -@item @code{-w register} @tab @code{--wide-register-format STR} @tab -@item @code{} @tab @code{--anon} @tab Print the ledger register with anonymized accounts and payees, useful for filing bug reports -@end multitable - -@node Grouping Options, Commodity Reporting Quick Reference, Output Customization Quick Reference, Command Line Quick Reference -@subsection Grouping Options -@multitable @columnfractions .1 .25 .65 -@item @strong{Short} @tab @strong{Long} @tab @strong{Description} -@item @code{-P} @tab @code{--by-payee} @tab Group postings by common payee names -@item @code{-D} @tab @code{--daily} @tab Group postings by day -@item @code{-W} @tab @code{--weekly} @tab Group postings by week -@item @code{-M} @tab @code{--Monthly} @tab Group postings by month -@item @code{} @tab @code{--quarterly} @tab Group postings by quarter -@item @code{-Y} @tab @code{--yearly} @tab Group postings by year -@item @code{} @tab @code{--dow} @tab Group by day of weeks -@item @code{-s} @tab @code{--subtotal} @tab Group posting together, similar to balance report -@end multitable - -@node Commodity Reporting Quick Reference, , Grouping Options, Command Line Quick Reference -@subsection Commodity Reporting - -@multitable @columnfractions .1 .25 .65 -@item @strong{Short} @tab @strong{Long} @tab @strong{Description} -@item @code{} @tab @code{--price-db FILE} @tab Use @file{FILE} for retrieving stored commodity prices -@item @code{-L MINS} @tab @code{--price-exp MINS} @tab Set expected freshness of prices in minutes -@item @code{-Q} @tab @code{--download} @tab Download quotes using @code{getquote} -@item @code{} @tab @code{--getquote} @tab Sets path to a user defined script to download commodity prices. -@item @code{-O} @tab @code{--quantity} @tab Report commodity totals without conversion -@item @code{-B} @tab @code{--basis} @tab Report cost basis -@item @code{-V} @tab @code{--market} @tab Report last known market value -@item @code{-G} @tab @code{--gain} @tab Report net gain loss for commodities that have a price history -@end multitable @node Principles of Accounting, Keeping a Journal, Ledger Tutorial , Top @chapter Principles of Accounting with Ledger @@ -770,7 +637,7 @@ 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}. +@code{Expenses:Auto:Gas} and @code{Expenses:Auto:Repair}. @menu * Tracking reimbursable expenses:: @@ -872,8 +739,8 @@ It's easier shown than said: 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 +money simply ``waited''---in both @code{Assets:Reimbursements:Company +XYZ}, and @code{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 @@ -917,7 +784,7 @@ apply account Company XYZ end apply account @end smallexample -(Note: The @samp{apply account} above means that all accounts mentioned in +(Note: The @code{apply 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). @@ -983,8 +850,8 @@ 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 +Specify the price history to use with the @code{--price-db} option, +with the @code{-V} option to report in terms of current market value: @example @@ -1054,7 +921,7 @@ 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 postings, one which -transfers an hour of time into a @samp{Billable} account, and another +transfers an hour of time into a @code{Billable} account, and another which decreases the same account by ten minutes. The resulting report will indicate that fifty minutes remain: @@ -1088,13 +955,13 @@ 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}) +Each of these definitions correlates a commodity (such as @code{Kb}) and a default precision, with a certain quantity of another commodity. In the above example, kilobytes are reported 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 +would report as a decimal amount (less than @code{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. @@ -1119,8 +986,8 @@ EverQuest account: 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 +have to use @code{Places:Black's Tavern} as the source account. You +could use @code{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. @@ -1173,7 +1040,7 @@ 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 +@code{-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, Principles of Accounting @@ -1186,7 +1053,7 @@ 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: +it as already spent to an @code{Expenses:Cash} category: @smallexample 2004/03/15 ATM @@ -1195,7 +1062,7 @@ it as already spent to an @samp{Expenses:Cash} category: @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 +just ``move'' the amount of the expense from @code{Expenses:Cash} into the target account: @smallexample @@ -1235,8 +1102,8 @@ reserves resources for later: 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 +one-to-one. What if the school fund has @code{$500.00}, but +@code{$400.00} of that comes from Checking, and @code{$100.00} from Savings? Traditional finance packages require that the money reside in only one @@ -1274,14 +1141,14 @@ account: 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 +@code{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 +If the @code{--real} option is used, the report will be in terms of the real accounts: @example @@ -1303,7 +1170,7 @@ The second way of tracking funds is to use transaction codes. In this respect the codes become like virtual accounts that embrace the entire set of postings. Basically, we are associating a transaction with a fund by setting its code. Here are two transactions that deposit money -into, and spend money from, the @samp{Funds:School} fund: +into, and spend money from, the @code{Funds:School} fund: @smallexample 2004/03/25 (Funds:School) Donations @@ -1320,10 +1187,10 @@ balance or registers reports will reflect this. That the transactions 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 +@code{--code-as-payee} option, you can generate a register report where the payee for each posting 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 +@code{--by-payee} option, you will now see account subtotals for any postings related to a specific fund. So, to see the current monetary balances of all funds, the command would be: @@ -1331,11 +1198,11 @@ monetary balances of all funds, the command would be: ledger --code-as-payee -P reg ^Assets @end smallexample -Or to see a particular funds expenses, the @samp{School} fund in this +Or to see a particular funds expenses, the @code{School} fund in this case: @smallexample -ledger --code-as-payee -P reg ^Expenses -- School +ledger --code-as-payee -P reg ^Expenses @@School @end smallexample Both approaches yield different kinds of flexibility, depending on how @@ -1384,8 +1251,8 @@ posting. * Currency and Commodities:: * Keeping it Consistent:: * Journal Format:: +* Converting from other formats:: * Archiving Previous Years :: -* Using Emacs:: @end menu @node Most Basic Entry, Starting up, Keeping a Journal, Keeping a Journal @@ -1413,7 +1280,7 @@ balanced amount, if it is the same as the first line: @end smallexample For this transaction, Ledger will figure out that $-23.00 must come from -@samp{Assets:Checking} in order to balance the transaction. +@code{Assets:Checking} in order to balance the transaction. Also note the structure of the account entries. There is an implied hierarchy established by separating with colons (see @pxref{Structuring @@ -1455,9 +1322,9 @@ your opening balance entry could look like this: Assets:Joint Checking $800.14 Assets:Other Checking $63.44 Assets:Savings $2805.54 - Assets:Investments:401K:Deferred 100.0000 VIFSX @ $80.5227 - Assets:Investments:401K:Matching 50.0000 VIFSX @ $83.7015 - Assets:Investments:IRA 250.0000 VTHRX @ $20.5324 + Assets:Investments:401K:Deferred 100.0000 VIFSX @@ $80.5227 + Assets:Investments:401K:Matching 50.0000 VIFSX @@ $83.7015 + Assets:Investments:IRA 250.0000 VTHRX @@ $20.5324 Liabilities:Mortgage $-175634.88 Liabilities:Car Loan $-3494.26 Liabilities:Visa -$1762.44 @@ -1692,7 +1559,7 @@ whose market value disregards any future changes in the price of gasoline. If you do not want price fixing, you can specify this same transaction -in one of two ways, both equivalent (note the lack of the equal sing +in one of two ways, both equivalent (note the lack of the equal sign from the transaction above): @smallexample @@ -1721,6 +1588,145 @@ its amount is null. @node Complete control over commodity pricing, , Fixing Lot Prices, Currency and Commodities @subsection Complete control over commodity pricing +Ledger allows you to have very detailed control over how your +commodities are valued. You can fine tune the results given using the +@code{--market} or @code{--exchange} options. There are now several +points of interception, you can specify the valuation method: +@enumerate + @item on a commodity itself + @item on a posting, via metadata (affect is largely the same as #1) + @item on an xact, which then applies to all postings in that xact + @item on any posting via an automated transaction + @item on a per-account basis + @item on a per-commodity basis + @item by changing the journal default of @code{market} +@end enumerate + +Fixated pricing (such as @code{@{=$20@})} still plays a role in this scheme. As far as +valuation goes, it's shorthand for writing @code{((s,d,t -> market($20,d,t)))}. + + +A valuation function receives three arguments: + +@table @code +@item source + A string identifying the commodity whose price is being asked for + (example: "EUR") +@item date + The reference date the price should be relative. +@item target +A string identifying the ``target'' commodity, or the commodity the + returned price should be in. This argument is null if @code{--market} + was used instead of @code{--exchange}. +@end table + +The valuation function should return an amount. If you've written your +function in Python, you can return something like @code{Amount("$100")}. If the +function returns an explicit value, that value is always used, regardless +of the commodity, the date, or the desired target commodity. For example, + +@smallexample +define myfunc_seven(s, d, t) = 7 EUR +@end smallexample + +In order to specify a fixed price, but still valuate that price into the +target commodity, use something like this: +@smallexample +define myfunc_five(s, d, t) = market(5 EUR, d, t) +@end smallexample + +The @code{value} directive sets the valuation used for all commodities +used in the rest of the data stream. This is the fallback, if nothing +more specific is found. +@smallexample +value myfunc_seven +@end smallexample + +You can set a specific valuation function on a per-commodity basis. +Instead of defining a function, you can also pass a lambda. +@smallexample +commodity $ + value s, d, t -> 6 EUR +@end smallexample + +Each account can also provide a default valuation function for any +commodities transferred to that account. + +@smallexample +account Expenses:Food5 + value myfunc_five +@end smallexample + +The metadata field @code{Value}, if found, overrides the valuation function +on a transaction-wide or per-posting basis. + +@smallexample += @@XACT and Food + ; Value:: 8 EUR + (Equity) $1 + += @@POST and Dining + (Expenses:Food9) $1 + ; Value:: 9 EUR +@end smallexample + +Lastly, you can specify the valuation function/value for any specific +amount using the @code{(( ))} commodity annotation. + +@smallexample +2012-03-02 KFC + Expenses:Food2 $1 ((2 EUR)) + Assets:Cash2 + +2012-03-03 KFC + Expenses:Food3 $1 + ; Value:: 3 EUR + Assets:Cash3 + +2012-03-04 KFC + ; Value:: 4 EUR + Expenses:Food4 $1 + Assets:Cash4 + +2012-03-05 KFC + Expenses:Food5 $1 + Assets:Cash5 + +2012-03-06 KFC + Expenses:Food6 $1 + Assets:Cash6 + +2012-03-07 KFC + Expenses:Food7 1 CAD + Assets:Cas7 + +2012-03-08 XACT + Expenses:Food8 $1 + Assets:Cash8 + +2012-03-09 POST + Expenses:Dining9 $1 + Assets:Cash9 +@end smallexample + + +@smallexample +ledger reg -V food +12-Mar-02 KFC Expenses:Food2 2 EUR 2 EUR +12-Mar-03 KFC <Adjustment> -1 EUR 1 EUR + Expenses:Food3 3 EUR 4 EUR +12-Mar-04 KFC <Adjustment> -2 EUR 2 EUR + Expenses:Food4 4 EUR 6 EUR +12-Mar-05 KFC <Adjustment> -3 EUR 3 EUR + Expenses:Food5 5 EUR 8 EUR +12-Mar-06 KFC <Adjustment> -4 EUR 4 EUR + Expenses:Food6 6 EUR 10 EUR +12-Mar-07 KFC Expenses:Food7 7 EUR 17 EUR +12-Mar-08 XACT Expenses:Food8 8 EUR 25 EUR +12-Mar-09 POST (Expenses:Food9) 9 EUR 34 EUR +@end smallexample + + @node Keeping it Consistent, Journal Format, Currency and Commodities, Keeping a Journal @section Keeping it Consistent @@ -1737,7 +1743,7 @@ account Expenses account Expenses:Utilities ... @end smallexample -Using the @samp{--strict} option will cause Ledger to complain if any accounts are not previously defined: +Using the @code{--strict} option will cause Ledger to complain if any accounts are not previously defined: @smallexample 15:27:39 ~/ledger (next) > ledger bal --strict Warning: "FinanceData/Master.dat", line 6: Unknown account 'Liabilities:Tithe Owed' @@ -1745,14 +1751,14 @@ Warning: "FinanceData/Master.dat", line 8: Unknown account 'Liabilities:Tithe Ow Warning: "FinanceData/Master.dat", line 15: Unknown account 'Allocation:Equities:Domestic' @end smallexample -If you have a large Ledger register already created use the @samp{accounts} command to get started: +If you have a large Ledger register already created use the @code{accounts} command to get started: @smallexample ledger accounts >> Accounts.dat @end smallexample -@noindent You will have to edit this file to add the @samp{account} directive. +@noindent You will have to edit this file to add the @code{account} directive. -@node Journal Format, Archiving Previous Years , Keeping it Consistent, Keeping a Journal +@node Journal Format, Converting from other formats, Keeping it Consistent, Keeping a Journal @section Journal Format The ledger file format is quite simple, but also very flexible. It @@ -1779,11 +1785,11 @@ transaction's account postings. The format of the first line is: DATE[=EDATE] [*|!] [(CODE)] DESC @end smallexample -If @samp{*} appears after the date (with optional effective date), it +If @code{*} appears after the date (with optional effective date), it indicates the transaction is ``cleared'', which can mean whatever the user -wants it to mean. If @samp{!} appears after the date, it indicates d +wants it to mean. If @code{!} appears after the date, it indicates d the transaction is ``pending''; i.e., tentatively cleared from the user's -point of view, but not yet actually cleared. If a @samp{CODE} appears +point of view, but not yet actually cleared. If a @code{CODE} appears in parentheses, it may be used to indicate a check number, or the type of the posting. Following these is the payee, or a description of the posting. @@ -1794,18 +1800,18 @@ The format of each following posting is: ACCOUNT AMOUNT [; NOTE] @end smallexample -The @samp{ACCOUNT} may be surrounded by parentheses if it is a virtual +The @code{ACCOUNT} may be surrounded by parentheses if it is a virtual posting, or square brackets if it is a virtual posting that -must balance. The @samp{AMOUNT} can be followed by a per-unit -posting cost, by specifying @samp{@@ AMOUNT}, or a complete -posting cost with @samp{@@@@ AMOUNT}. Lastly, the @samp{NOTE} may +must balance. The @code{AMOUNT} can be followed by a per-unit +posting cost, by specifying @code{@@ AMOUNT}, or a complete +posting cost with @code{@@@@ AMOUNT}. Lastly, the @code{NOTE} may specify an actual and/or effective date for the posting by using -the syntax @samp{[ACTUAL_DATE]} or @samp{[=EFFECTIVE_DATE]} or -@samp{[ACTUAL_DATE=EFFECTIVE_DATE]}.(See @pxref{Virtual postings}) +the syntax @code{[ACTUAL_DATE]} or @code{[=EFFECTIVE_DATE]} or +@code{[ACTUAL_DATE=EFFECTIVE_DATE]}.(See @pxref{Virtual postings}) @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 +in a pricing history file (see the @code{-Q} option). The syntax is: @smallexample P DATE SYMBOL PRICE @@ -1818,7 +1824,7 @@ sign. After this initial line there should be a set of one or more postings, just as if it were normal transaction. If the amounts of the postings have no commodity, they will be applied as modifiers to -whichever real posting is matched by the value expression(See @pxref{Automated transactions}). +whichever real posting is matched by the value expression(See @pxref{Automated Transactions}). @item ~ A period transaction. A period expression must appear after the tilde. @@ -1848,8 +1854,8 @@ Command directives must occur at the beginning of a line. Use of ! and @item account Pre-declare valid account names. This only has effect if -@samp{--strict} or @samp{--pedantic} is used (see below). The -@samp{account} directive supports several optional sub-directives, if +@code{--strict} or @code{--pedantic} is used (see below). The +@code{account} directive supports several optional sub-directives, if they immediately follow the account directive and if they begin with whitespace: @@ -1864,14 +1870,14 @@ whitespace: default @end smallexample -The @samp{note} sub-directive associates a textual note with the account. This can -be accessed later using the @samp{note} valexpr function in any account context. +The @code{note} sub-directive associates a textual note with the account. This can +be accessed later using the @code{note} valexpr function in any account context. -The @samp{alias} sub-directive, which can occur multiple times, allows the alias to +The @code{alias} sub-directive, which can occur multiple times, allows the alias to be used in place of the full account name anywhere that account names are allowed. -The @samp{payee} sub-directive, which can occur multiple times, provides regexps +The @code{payee} sub-directive, which can occur multiple times, provides regexps that identify the account if that payee is encountered and an account within its transaction ends in the name "Unknown". Example: @@ -1881,13 +1887,13 @@ its transaction ends in the name "Unknown". Example: Assets:Cash @end smallexample -The @samp{check} and @samp{assert} directives warn or error (respectively) if the given +The @code{check} and @code{assert} directives warn or error (respectively) if the given value expression evaluates to false within the context of any posting. -The @samp{eval} directive evaluates the value expression in the context of the +The @code{eval} directive evaluates the value expression in the context of the account at the time of definition. At the moment this has little value. -The @samp{default} directive specifies that this account should be used as the +The @code{default} directive specifies that this account should be used as the ``balancing account'' for any future transactions that contain only a single posting. @@ -1975,7 +1981,7 @@ account. For example: capture Expenses:Deductible:Medical Medical @end smallexample -Would cause any posting with @code{Medical} in it's name to be replaced with +Would cause any posting with @code{Medical} in its name to be replaced with @code{Expenses:Deductible:Medical}. @@ -1996,13 +2002,13 @@ check <VALUE EXPRESSION BOOLEAN RESULT> Start a block comment, closed by @code{end comment}. @item commodity -Pre-declare commodity names. This only has effect if @samp{--strict} or -@samp{--pedantic} is used (see below). +Pre-declare commodity names. This only has effect if @code{--strict} or +@code{--pedantic} is used (see below). commodity $ commodity CAD -The @samp{commodity} directive supports several optional sub-directives, if they +The @code{commodity} directive supports several optional sub-directives, if they immediately follow the commodity directive and if they begin with whitespace: @smallexample @@ -2013,18 +2019,18 @@ immediately follow the commodity directive and if they begin with whitespace: default @end smallexample -The @samp{note} sub-directive associates a textual note with the commodity. At +The @code{note} sub-directive associates a textual note with the commodity. At present this has no value other than documentation. -The @samp{format} directive gives you a way to tell Ledger how to format this +The @code{format} directive gives you a way to tell Ledger how to format this commodity. In future using this directive will disable Ledger's observation of other ways that commodity is used, and will provide the ``canonical'' representation. -The @samp{nomarket} directive states that the commodity's price should never be +The @code{nomarket} directive states that the commodity's price should never be auto-downloaded. -The @samp{default} directive marks this as the ``default'' commodity. +The @code{default} directive marks this as the ``default'' commodity. @item define @c instance_t::define_directive in textual.cc @@ -2047,13 +2053,48 @@ Closes block commands like @code{tag} or @code{comment}. @item fixed @c instance_t::fixed_directive in textual.cc +A fixed block is used to set fixated prices (@pxref{Fixated prices}) for a series of +transactions. It's purely a typing saver, for use when entering many +transactions with fixated prices. + +Thus, the following: +@smallexample +fixed CAD $0.90 + 2012-04-10 Lunch in Canada + Assets:Wallet -15.50 CAD + Expenses:Food 15.50 CAD + + 2012-04-11 Second day Dinner in Canada + Assets:Wallet -25.75 CAD + Expenses:Food 25.75 CAD +endfixed +@end smallexample +is equivalent to this: +@smallexample + 2012-04-10 Lunch in Canada + Assets:Wallet -15.50 CAD @{=$0.90@} + Expenses:Food 15.50 CAD @{=$0.90@} + + 2012-04-11 Second day Dinner in Canada + Assets:Wallet -25.75 CAD @{=$0.90@} + Expenses:Food 25.75 CAD @{=$0.90@} +@end smallexample + +Note that ending a @code{fixed} is done differently than other +directives, as @code{fixed} is closed with an @code{endfixed} (i.e., +there is @strong{no space} between @code{end} and @code{fixed}). + +For the moment, users may wish to study +@uref{http://bugs.ledger-cli.org/show_bug.cgi?id=789, Bug Report 789} +before using the @code{fixed} directive in production. + @item include @c instance_t::include_directive in textual.cc Include the stated file as if it were part of the current file. @item payee @c instance_t::payee_mapping_directive in textual.cc -The @samp{payee} directive supports one optional sub-directive, if it immediately +The @code{payee} directive supports one optional sub-directive, if it immediately follows the payee directive and if it begins with whitespace: @smallexample @@ -2061,7 +2102,7 @@ follows the payee directive and if it begins with whitespace: alias KENTUCKY FRIED CHICKEN @end smallexample -The @samp{alias} directive provides a regexp which, if it matches a parsed payee, +The @code{alias} directive provides a regexp which, if it matches a parsed payee, the declared payee name is substituted: @smallexample @@ -2121,8 +2162,8 @@ Note that anything following ``@code{end tag}'' is ignored. placing the name of the tag that is being closed is a simple way to keep track. @item tag -Pre-declares tag names. This only has effect if @samp{--strict} or -@samp{--pedantic} is used (see below). +Pre-declares tag names. This only has effect if @code{--strict} or +@code{--pedantic} is used (see below). @smallexample tag Receipt @@ -2138,7 +2179,7 @@ follow the tag directive and if they begin with whitespace: assert value != "foobar" @end smallexample -The @samp{check} and @samp{assert} directives warn or error (respectively) if the given +The @code{check} and @code{assert} directives warn or error (respectively) if the given value expression evaluates to false within the context of any use of the related tag. In such a context, ``value'' is bound to the value of the tag (which may not be a string if typed-metadata is used!). Such checks or @@ -2146,13 +2187,13 @@ assertions are not called if no value is given. @item test @c instance_t::comment_directive in textual.cc -This is a synonym for @code{comment} and must be closed by and @code{end} tag. +This is a synonym for @code{comment} and must be closed by an @code{end} tag. @item year @c instance_t::year_directive in textual.cc Denotes the year used for all subsequent transactions that give a date -without a year. The year should appear immediately after the Y, for -example: @samp{year 2004}. This is useful at the beginning of a file, to +without a year. The year should appear immediately after the directive, for +example: @code{year 2004}. This is useful at the beginning of a file, to specify the year for that file. If all transactions specify a year, however, this command has no effect. @@ -2205,7 +2246,21 @@ timelog files. See the timeclock's documentation for more info on the syntax of its timelog files. @end table -@node Archiving Previous Years , Using Emacs, Journal Format, Keeping a Journal +@node Converting from other formats, Archiving Previous Years , Journal Format, Keeping a Journal +@section Converting from other formats +There are numerous tools to help convert various formats to a Ledger +file. Most banks will generate a commas separated value file that can +easily be parsed into Ledger format using one of those tools. Some of the more popular tools are: +@itemize +@item @code{icsv2ledger} +@item @code{csvToLedger} +@item @code{CSV2Ledger} +@end itemize +@noindent Directly pulling information from banks is outside the scope of Ledger's +function. + + +@node Archiving Previous Years , , Converting from other formats, Keeping a Journal @section Archiving Previous Years @@ -2254,7 +2309,7 @@ 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 +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 @@ -2264,208 +2319,6 @@ 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 Using Emacs, , Archiving Previous Years , Keeping a Journal -@section Using Emacs to Maintain Your Journal -@cindex Emacs - -@menu -* running ledger-mode:: -* Working with entries:: -* Reconciling accounts:: -* Generating Reports:: -@end menu - -@node running ledger-mode, Working with entries, Using Emacs, Using Emacs -@subsection Running ledger-mode - -Journal files are simple free text files easily modified by any text -editor. A special mode for Emacs is included with the source -distribution. - -@cindex Emacs .emacs file -To use the Emacs mode, copy the several lisp files from the source lisp -directory your your @file{site-lisp} directory and add the following line -to your @file{.emacs} (or equivalent, @file{~/Aquamacs/Preferences.el} -for Aquamacs on Mac OS X) -@smallexample -(load "ledger") -@end smallexample - -To trigger ledger mode when you visit a journal file, the first line of -each of your journal files should be: -@smallexample -; -*-ledger-*- -@end smallexample -To enter ledger-mode on a new file, type M-x ledger-mode. - -Once you have loaded a Journal file into Emacs, you have several -commands available to make entering, clearing and reconciling -transactions and producing reports: - -@cindex Emacs commands -@table @code -@item C-i or <TAB> -auto complete entry -@item C-c C-a -add a new entry, based on previous entries -@item C-c C-e -toggle cleared status of an entire entry -@item C-c C-c -toggle cleared status of an individual posting -@item C-c C-y -set default year for entry mode -@item C-c C-m -set default month for entry mode -@item C-c C-r -reconcile uncleared entries related to an account -@item C-c C-d -delete the current entry -@item C-c C-s -sort all entries in the journal by date. Drop comments outside of entries -@item C-c C-o C-r -run a ledger report -@item C-C C-o C-g -go to the ledger report buffer -@item C-c C-o C-e -edit the defined ledger reports -@item C-c C-o C-s -save a report definition based on the current report -@item C-c C-o C-a -rerun a ledger report -@item C-c C-o C-k -kill the ledger report buffer -@end table - -@menu -* Working with entries:: -* Reconciling accounts:: -* Generating Reports:: -@end menu - -@node Working with entries, Reconciling accounts, running ledger-mode, Using Emacs -@subsection Working with entries -@menu -* Manual Entry Support:: -* Automagically Adding new entries:: -* Clearing Transactions:: -@end menu - -@node Manual Entry Support, Automagically Adding new entries, Working with entries, Working with entries -@subsubsection Manual Entry Support - -@cindex <TAB> completion -@cindex auto-completion -@cindex misspelled accounts treated as new - -In most financial programs, some sort of auto-completion is available to -save typing and improve accuracy. Ledger doesn't leave you hanging, -@code{ledger-mode} provides tab completion on all portions of an entry. -Type a portion of the payee and hit <TAB>, and @code{ledger-mode} will -suggest a completion. When filling in the account type the first few -letters followed by a <TAB> and the account will be filled in. For -example typing @samp{Ex<TAB>Au<TAB>F<TAB>} would yield -@samp{Expenses:Auto:Fuel} if you had previously used that account in -this journal. If there are more than one account with similar starting, -hitting <TAB> multiple times will iterate through them. This is a good -habit to get in to prevent misspellings of accounts. Remember Ledger -does not validate the names of payees or account so a misspelled account -will be interpreted as a new account by ledger. - - -@node Automagically Adding new entries, Clearing Transactions, Manual Entry Support, Working with entries -@subsubsection Automagically Adding new entries -@cindex new transactions in Emacs -@cindex Emacs, adding new transactions -@code{C-c C-a} will run the @code{ledger entry} command (@pxref{entry -and xact}) from within Emacs. When typed, the mini-buffer will appear -with the current year and month, waiting for you to enter the day and -the payee. Ledger will generate a new entry based on the most recent -entry for that payee, using the amount and accounts from that -transaction. If you have a new amount simply type the amount after the -payee. For example, if your journal contains an entry -@smallexample -2011/11/25 Viva Italiano - Expenses:Food $12.45 - Expenses:Tips $2.55 - Liabilities:MasterCard $-15.00 -@end smallexample -@noindent and you type @samp{C-c C-a}, the mini-buffer will appear showing the -current year and month. If you complete the mini-buffer entry by typing -@smallexample -Entry: 2011/11/28 viva food 34 tip 7 <enter> -@end smallexample -@noindent Emacs will add the following entry to your journal: -@smallexample -2011/11/30 Viva Italiano - Expenses:Food $34.00 - Expenses:Tips $7.00 - Liabilities:MasterCard -@end smallexample -@noindent Notice that the entry will appear at the correct place in the journal -ordered by date, not necessarily at the bottom of the file. -@node Clearing Transactions, , Automagically Adding new entries, Working with entries -@subsubsection Clearing Transactions and Postings -@cindex clearing transactions in Emacs -@cindex Emacs, clear transaction -@code{C-c C-e} will place an asterisk after the date in the current -transaction. The tells ledger the transaction has been cleared through -your bank (or whatever else you want the concept to mean) -@smallexample -2011/11/25 Viva Italiano - Expenses:Food $12.45 - Expenses:Tips $2.55 - Liabilities:MasterCard $-15.00 -@end smallexample -@noindent becomes -@smallexample -2011/11/25 * Viva Italiano - Expenses:Food $12.45 - Expenses:Tips $2.55 - Liabilities:MasterCard $-15.00 -@end smallexample - -If, for some reason you need to clear a specific posting in the -transaction you can type @samp{C-c C-c} and the posting at point will be -toggled. - -@node Reconciling accounts, Generating Reports, Working with entries, Using Emacs -@subsection Reconciling accounts - -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). - -@node Generating Reports, , Reconciling accounts, Using Emacs -@subsection Generating Reports - -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: -@table @code -@item (space) -scroll up -@item e -edit the defined ledger reports -@item s -save a report definition based on the current report -@item q -quit the report (return to ledger buffer) -@item r -redo the report -@item k -kill the report buffer -@end table - - - - @node Transactions , Building Reports, Keeping a Journal, Top @chapter Transactions @menu @@ -2486,10 +2339,11 @@ kill the report buffer * Virtual posting costs:: * Commodity prices:: * Prices vs. costs:: +* Fixated prices:: * Lot dates:: * Lot notes:: * Lot value expressions:: -* Automated transactions:: +* Automated Transactions:: @end menu @node Basic format, Eliding amounts, Transactions , Transactions @@ -2736,9 +2590,9 @@ cannot appear in the Key: @node Typed metadata, , Metadata values, Metadata @subsection Typed metadata -If a metadata tag ends in ::, it's value will be parsed as a value expression -and stored internally as a value rather than as a string. For example, -although I can specify a date textually like so: +If a metadata tag ends in ::, its value will be parsed as a value +expression and stored internally as a value rather than as a string. +For example, although I can specify a date textually like so: @smallexample 2012-03-10 * KFC @@ -2747,10 +2601,10 @@ although I can specify a date textually like so: ; AuxDate: 2012/02/30 @end smallexample -@noindent This date is just a string, and won't be parsed as a date unless its value is -used in a date-context (at which time the string is parsed into a date -automatically every time it is needed as a date). If on the other hand I -write this: +@noindent This date is just a string, and won't be parsed as a date +unless its value is used in a date-context (at which time the string +is parsed into a date automatically every time it is needed as a +date). If on the other hand I write this: @smallexample 2012-03-10 * KFC @@ -2759,8 +2613,9 @@ write this: ; AuxDate:: [2012/02/30] @end smallexample -@noindent Then it is parsed as a date only once, and during parsing of the journal file, -which would let me know right away that it is an invalid date. +@noindent Then it is parsed as a date only once, and during parsing +of the journal file, which would let me know right away that it is an +invalid date. @node Virtual postings, Expression amounts, Metadata, Transactions @section Virtual postings @@ -2771,7 +2626,7 @@ there are some tricks up Ledger's sleeve... You can use virtual accounts to transfer amounts to an account on the sly, bypassing the balancing requirement. The trick is that these postings are not -considered ``real'', and can be removed from all reports using @samp{--real}. +considered ``real'', and can be removed from all reports using @code{--real}. To specify a virtual account, surround the account name with parentheses: @@ -2917,12 +2772,12 @@ resulting posting cost is $50.00 per share. @node Explicit posting costs, Posting cost expressions, Posting cost, Transactions @section Explicit posting costs -You can make any posting's cost explicit using the @ symbol after the amount +You can make any posting's cost explicit using the @@ symbol after the amount or amount expression: @smallexample 2012-03-10 My Broker - Assets:Brokerage 10 AAPL @ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $-500.00 @end smallexample @@ -2931,7 +2786,7 @@ the first posting's cost, you can elide the other amount: @smallexample 2012-03-10 My Broker - Assets:Brokerage 10 AAPL @ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash @end smallexample @@ -2948,7 +2803,7 @@ source account. Whenever a commodity is exchanged like this, the commodity moved to the target account is considered "secondary", while the commodity used for purchasing and tracked in the cost is "primary". -Said another way, whenever Ledger sees a posting cost of the form "AMOUNT @ +Said another way, whenever Ledger sees a posting cost of the form "AMOUNT @@ AMOUNT", the commodity used in the second amount is marked "primary". The only meaning a primary commodity has is that -V flag will never convert a @@ -2961,7 +2816,7 @@ Just as you can have amount expressions, you can have posting expressions: @smallexample 2012-03-10 My Broker - Assets:Brokerage 10 AAPL @ ($500.00 / 10) + Assets:Brokerage 10 AAPL @@ ($500.00 / 10) Assets:Brokerage:Cash @end smallexample @@ -2969,20 +2824,20 @@ You can even have both: @smallexample 2012-03-10 My Broker - Assets:Brokerage (5 AAPL * 2) @ ($500.00 / 10) + Assets:Brokerage (5 AAPL * 2) @@ ($500.00 / 10) Assets:Brokerage:Cash @end smallexample @node Total posting costs, Virtual posting costs, Posting cost expressions, Transactions @section Total posting costs -The cost figure following the @ character specifies the @emph{per-unit} price for +The cost figure following the @@ character specifies the @emph{per-unit} price for the commodity being transferred. If you'd like to specify the total cost instead, use @@@@: @smallexample 2012-03-10 My Broker - Assets:Brokerage 10 AAPL @@@@ $500.00 + Assets:Brokerage 10 AAPL @@ $500.00 Assets:Brokerage:Cash @end smallexample @@ -3014,7 +2869,7 @@ of an exceptional transaction, surround the @@ or @@@@ with parentheses: When a transaction occurs that exchange one commodity for another, Ledger records that commodity price not only within its internal price database, but also attached to the commodity itself. Usually this fact remains invisible to -the user, unless you turn on @samp{--lot-prices} to show these hidden price figures. +the user, unless you turn on @code{--lot-prices} to show these hidden price figures. For example, consider the stock sale given above: @@ -3096,7 +2951,7 @@ Plus, it comes with dangers. This works fine: @smallexample 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $750.00 2012-04-10 My Broker @@ -3114,7 +2969,7 @@ Plus, it comes with dangers. This works fine: @smallexample 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $750.00 2012-04-10 My Broker @@ -3131,7 +2986,7 @@ Plus, it comes with dangers. This works fine: And in cases where the amounts do not divide into whole figure and must be rounded, the capital gains figure could be off by a cent. Use with caution. -@node Prices vs. costs, Lot dates, Commodity prices, Transactions +@node Prices vs. costs, Fixated prices, Commodity prices, Transactions @section Prices vs. costs Because lot pricing provides enough information to infer the cost, the @@ -3139,7 +2994,7 @@ following two transactions are equivalent: @smallexample 2012-04-10 My Broker - Assets:Brokerage 10 AAPL @ $50.00 + Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $750.00 2012-04-10 My Broker @@ -3151,7 +3006,8 @@ However, note that what you see in some reports may differ, for example in the print report. Functionally, however, there is no difference, and neither the register nor the balance report are sensitive to this difference. -@section Fixated prices +@node Fixated prices, Lot dates, Prices vs. costs, Transactions +@section Fixated prices and costs If you buy a stock last year, and ask for its value today, Ledger will consult its price database to see what the most recent price for that stock is. You @@ -3170,14 +3026,7 @@ else happens to the stock in the meantime. Fixated prices are a special case of using lot valuation expressions (see below) to fix the value of a commodity lot. -@menu -* Fixated costs:: -@end menu - -@node Fixated costs, , Prices vs. costs, Prices vs. costs -@subsection Fixated costs - -Since price annotations are costs are largely interchangeable and a matter of +Since price annotations and costs are largely interchangeable and a matter of preference, there is an equivalent syntax for specified fixated prices by way of the cost: @@ -3188,13 +3037,13 @@ of the cost: @end smallexample This is the same as the previous transaction, with the same caveats found in -the section ``Prices vs. costs''. +@ref{Prices vs. costs}. -@node Lot dates, Lot notes, Prices vs. costs, Transactions +@node Lot dates, Lot notes, Fixated prices, Transactions @section Lot dates In addition to lot prices, you can specify lot dates and reveal them with -@samp{--lot-dates}. Other than that, however, they have no special meaning to +@code{--lot-dates}. Other than that, however, they have no special meaning to Ledger. They are specified after the amount in square brackets (the same way that dates are parsed in value expressions): @@ -3210,7 +3059,7 @@ that dates are parsed in value expressions): You can also associate arbitrary notes for your own record keeping in parentheses, and reveal them with --lot-notes. One caveat is that the note -cannot begin with an @ character, as that would indicate a virtual cost: +cannot begin with an @@ character, as that would indicate a virtual cost: @smallexample 2012-04-10 My Broker @@ -3222,9 +3071,9 @@ cannot begin with an @ character, as that would indicate a virtual cost: You can any combination of lot prices, dates or notes, in any order. They are all optional. -To show all lot information in a report, use @samp{--lots}. +To show all lot information in a report, use @code{--lots}. -@node Lot value expressions, Automated transactions, Lot notes, Transactions +@node Lot value expressions, Automated Transactions, Lot notes, Transactions @section Lot value expressions Normally when you ask Ledger to display the values of commodities held, it @@ -3291,8 +3140,8 @@ In most cases, it is simplest to either use explicit amounts in your valuation expressions, or just pass the arguments down to market after modifying them to suit your needs. -@node Automated transactions, , Lot value expressions, Transactions -@section Automated transactions +@node Automated Transactions, , Lot value expressions, Transactions +@section Automated Transactions An automated transaction is a special kind of transaction which adds its postings to other transactions any time one of that other transactions' @@ -3344,9 +3193,10 @@ transaction. * State flags:: * Effective Dates:: * Periodic Transactions:: +* Concrete Example of Automated Transactions:: @end menu -@node Amount multipliers, Accessing the matching posting's amount, Automated transactions, Automated transactions +@node Amount multipliers, Accessing the matching posting's amount, Automated Transactions, Automated Transactions @subsection Amount multipliers As a special case, if an automated transaction's posting's amount (phew) has @@ -3375,7 +3225,7 @@ Then the latter transaction turns into this during parsing: Bar $-1000.00 @end smallexample -@node Accessing the matching posting's amount, Referring to the matching posting's account, Amount multipliers, Automated transactions +@node Accessing the matching posting's amount, Referring to the matching posting's account, Amount multipliers, Automated Transactions @subsection Accessing the matching posting's amount If you use an amount expression for an automated transaction's posting, that @@ -3402,7 +3252,7 @@ This becomes: (Foo) $-40.00 @end smallexample -@node Referring to the matching posting's account, Applying metadata to every matched posting, Accessing the matching posting's amount, Automated transactions +@node Referring to the matching posting's account, Applying metadata to every matched posting, Accessing the matching posting's amount, Automated Transactions @subsection Referring to the matching posting's account Sometimes want to refer to the account that matched in some way within the @@ -3427,7 +3277,7 @@ Becomes: Assets:Cash $-20.00 @end smallexample -@node Applying metadata to every matched posting, Applying metadata to the generated posting, Referring to the matching posting's account, Automated transactions +@node Applying metadata to every matched posting, Applying metadata to the generated posting, Referring to the matching posting's account, Automated Transactions @subsection Applying metadata to every matched posting If the automated transaction has a transaction note, that note is copied @@ -3453,7 +3303,7 @@ Becomes: Assets:Cash $-20.00 @end smallexample -@node Applying metadata to the generated posting, State flags, Applying metadata to every matched posting, Automated transactions +@node Applying metadata to the generated posting, State flags, Applying metadata to every matched posting, Automated Transactions @subsection Applying metadata to the generated posting If the automated transaction's posting has a note, that note is carried to the @@ -3483,14 +3333,14 @@ This is slightly different from the rules for regular transaction notes, in that an automated transaction's note does not apply to every posting within the automated transaction itself, but rather to every posting it matches. -@node State flags, Effective Dates, Applying metadata to the generated posting, Automated transactions +@node State flags, Effective Dates, Applying metadata to the generated posting, Automated Transactions @subsection State flags Although you cannot mark an automated transaction as a whole as cleared or pending, you can mark its postings with a * or ! before the account name, and that state flag gets carried to the generated posting. -@node Effective Dates, Periodic Transactions, State flags, Automated transactions +@node Effective Dates, Periodic Transactions, State flags, Automated Transactions @subsection Effective Dates @cindex effective dates @@ -3557,16 +3407,93 @@ automatic $37.50 deficit like you should, while your checking account really knows that it debited $225 this month. -@node Periodic Transactions, , Effective Dates, Automated transactions +@node Periodic Transactions, Concrete Example of Automated Transactions, Effective Dates, Automated Transactions @subsection Periodic Transactions A periodic transaction starts with a ~ followed by a period expression. Periodic transactions are used for budgeting and forecasting only, they -have no effect without the @samp{--budget} option specified. +have no effect without the @code{--budget} option specified. See @ref{Budgeting and Forecasting} for examples and details. +@node Concrete Example of Automated Transactions, , Periodic Transactions, Automated Transactions +@subsection Concrete Example of 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 posting at the top of your ledger file: + +@smallexample +; This automated transaction will compute Huqúqu'lláh based on this +; journal's postings. Any that match will affect the +; Liabilities:Huququ'llah account by 19% of the value of that posting. + += /^(?:Income:|Expenses:(?:Business|Rent$|Furnishings|Taxes|Insurance))/ + (Liabilities:Huququ'llah) 0.19 +@end smallexample + +This automated posting works by looking at each posting in the +ledger file. If any match the given value expression, 19% of the +posting'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 transaction 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 transactions, use: + +@smallexample +ledger balance Liabilities:Huquq +@end smallexample + +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 +posting matched your automated transaction'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 posting, +which may be excluded from reports by using @option{--real}. @@ -3640,7 +3567,7 @@ command. $ 5,480.00 20:39:21 ~/ledger/test/input > @end smallexample -@noindent note the implicit logical and between @samp{Auto} and @samp{Mastercard}. +@noindent note the implicit logical and between @code{Auto} and @code{Mastercard}. If you want the entire contents of a branch of your account tree, use the highest common name in the branch: @@ -3663,7 +3590,7 @@ You can use general regular expressions in nearly anyplace Ledger needs a string The first example looks for any account starting with ``Bo'', of which there are none. The second looks for any account with ``Bo'', which is -@samp{Expenses:Books}. +@code{Expenses:Books}. @cindex limit by payees If you want to know exactly how much you have spent in a particular @@ -3714,22 +3641,22 @@ 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 +ledger -M --period-sort "(amount)" 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 +things. To see that report, add @code{-r}, which shows the ``related account'' postings: @example -ledger -M --period-sort t -r reg ^expenses +ledger -M --period-sort "(amount)" -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 postings -calculated must match @samp{^expenses}, while the postings -displayed must match @samp{mastercard}. The command would be: +calculated must match @code{^expenses}, while the postings +displayed must match @code{mastercard}. The command would be: @example ledger -M -r --display "account =~ /mastercard/" reg ^expenses @@ -3737,8 +3664,8 @@ ledger -M -r --display "account =~ /mastercard/" reg ^expenses This query says: Report monthly subtotals; report the ``related account'' postings; display only related postings whose -account matches @samp{mastercard}, and base the calculation on -postings matching @samp{^expenses}. +account matches @code{mastercard}, and base the calculation on +postings matching @code{^expenses}. This works just as well for report the overall total, too: @@ -3746,7 +3673,7 @@ This works just as well for report the overall total, too: ledger -s -r --display "account =~ /mastercard/"/ reg ^expenses @end example -The @option{-s} option subtotals all postings, just as @option{-M} +The @code{-s} option subtotals all postings, just as @code{-M} subtotaled by the month. The running total in both cases is off, however, since a display expression is being used. @@ -3882,8 +3809,8 @@ register reports. The script to do this is included in the ledger distribution, and is named @file{contrib/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 (--amount-data)} or -@option{-J (--total-data)} to indicate whether Gnuplot should plot the +in mind is that you must specify @code{-j (--amount-data)} or +@code{-J (--total-data)} to indicate whether Gnuplot should plot the amount, or the running total. For example, this command plots total monthly expenses made on your MasterCard. @@ -3914,8 +3841,8 @@ report -J -l "Ua>=@{\$0.01@}" reg ^assets ^liab 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 last report uses both a calculation predicate (@code{-l}) and a +display predicate (@code{-d}). The calculation predicates limits the report to postings whose amount is greater than $1 (which can only happen if the posting amount is in dollars). The display predicate limits the transactions @emph{displayed} to just those since last @@ -3932,7 +3859,6 @@ of the balance. * Primary Financial Reports:: Reports in other formats:: Reports about * Reports in other Formats:: * Reports about your Journals:: -* Developer Commands:: @end menu @node Primary Financial Reports, Reports in other Formats, Reporting Commands, Reporting Commands @@ -3978,7 +3904,7 @@ 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 +add either @code{-j} or @code{-J} to your register command, in order to plot either the amount or total column, respectively. @node The print Command, , The register Command, Primary Financial Reports @@ -3998,14 +3924,14 @@ file whose formatting has gotten out of hand. @section Reports in other Formats @menu * Comma Separated Variable files:: -* Emacs:: -* Emacs org mode:: +* The lisp command:: +* Emacs Org mode:: * The pricemap Command:: * The xml Command:: * prices and pricedb:: @end menu -@node Comma Separated Variable files, Emacs, Reports in other Formats, Reports in other Formats +@node Comma Separated Variable files, The lisp command, Reports in other Formats, Reports in other Formats @subsection Comma Separated Variable files @menu * The csv command:: @@ -4014,19 +3940,19 @@ file whose formatting has gotten out of hand. @node The csv command, The convert command, Comma Separated Variable files, Comma Separated Variable files @subsubsection The @code{csv} command -The csv command will output print out the desired ledger transactions in -a csv format suitable for import into other programs. You can determine -the transaction to print using all the normal limiting and searching +The @command{csv} command will output print out the desired ledger transactions in +a csv format suitable for import into other programs. You can specify +the transactions to print using all the normal limiting and searching functions. @cindex csv conversion @cindex reading csv @cindex comma separated variable file reading @node The convert command, , The csv command, Comma Separated Variable files @subsubsection The @code{convert} command -Convert reads your Ledger journal then parses a comma separated value -(csv) file into Ledger transactions. Many banks offer csv file -downloads. Unfortunately the file formats, aside form the commas, are -all different. The ledger convert command tried to help as much as it +The @code{convert} command parses a comma separated value +(csv) file and outputs Ledger transactions. Many banks offer csv file +downloads. Unfortunately, the file formats, aside the from commas, are +all different. The ledger @code{convert} command tries to help as much as it can. Your banks csv files will have fields in different orders from other @@ -4058,7 +3984,7 @@ line of the file. The fields ledger can recognize are called Delete the account description lines at the top, and replace the first line in the data above with: @smallexample -date,payee,note,amount,,,code, +,date,payee,note,amount,,,code, @end smallexample Then execute ledger like this: @@ -4070,10 +3996,53 @@ Where the @code{--input-date-format} option tells ledger how to interpret the dates. Importing csv files is a lot of work, and but is very amenable to scripting. -@node Emacs, Emacs org mode, Comma Separated Variable files, Reports in other Formats -@subsection Emacs -The @command{emacs} command outputs results in a form that can be read +If there are columns in the bank data you would like to keep in your +ledger data, besides the primary fields described above, you can name +them in the field descriptor list and Ledger will include them in the +transaction as meta data if it doesn't recognize the field name. For +example, if you want to capture the bank transaction number and it +occurs in the first column of the data use: + + +@smallexample +transid,date,payee,note,amount,,,code, +@end smallexample + +Ledger will include @code{; transid: 767718} in the first transaction is +from the file above. + +The @code{convert} command accepts three options, the most important +ones are @code{--invert} which inverts the amount field, and +@code{--account NAME} which you can use to specify the account to +balance against and @code{--rich-data}. When using the rich-data switch +additional metadata is stored as tags. There is, for example, a UUID field. If +an entry with the same UUID tag is already included in the normal ledger +file (specified via @code{-f} or @code{$LEDGER_FILE}) this entry will not be printed +again. + +You can also use @code{convert} with @code{payee} and @code{account} +directives. First, you can use the @code{payee} and @code{alias} +directive to rewrite the @code{payee} field based on some rules. Then you can +use the account and its @code{payee} directive to specify the account. I use it +like this, for example: + +@smallexample +payee Aldi + alias ^ALDI SUED SAGT DANKE +account Aufwand:Einkauf:Lebensmittel + payee ^(Aldi|Alnatura|Kaufland|REWE)$ +@end smallexample + +Note that it may be necessary for the output of @code{ledger convert} to be +passed through @code{ledger print} a second time if you want to match on the +new payee field. During the @code{ledger convert} run only the original payee +name as specified in the csv data seems to be used. + +@node The lisp command, Emacs Org mode, Comma Separated Variable files, Reports in other Formats +@subsection The @code{lisp} command + +The @command{lisp} command outputs results in a form that can be read directly by Emacs Lisp. The format of the @code{sexp} is: @smallexample @@ -4082,24 +4051,26 @@ directly by Emacs Lisp. The format of the @code{sexp} is: ...) ; list of transactions @end smallexample -@node Emacs org mode, The pricemap Command, Emacs, Reports in other Formats +@noindent @code{emacs} can also be used as a synonym for @code{lisp} + +@node Emacs Org mode, The pricemap Command, The lisp command, Reports in other Formats @subsection Emacs @code{org} Mode The @code{org} command produces a journal file suitable for use in the -Emacs org mode. More details on using org mode can be found at +Emacs Org mode. More details on using Org mode can be found at @url{http://www.orgmode.org}. -Org mode has a sub-system known as babel which allows for literate +Org mode has a sub-system known as Babel which allows for literate programming. This allows you to mix text and code within the same document and automatically execute code which may generate results which will then appear in the text. -One of the languages supported by org+babel is ledger so that you can +One of the languages supported by @code{org+babel} is Ledger, so that you can have ledger commands embedded in a text file and have the output of ledger commands also appear in the text file. The output can be updated whenever any new ledger entries are added. -For instance, the following org mode text document snippet illustrates a -very naive but still useful of the org+babel system: +For instance, the following Org mode text document snippet illustrates a +very naive but still useful of the @code{org+babel} system: @smallexample * A simple test of ledger in an org file @@ -4139,7 +4110,7 @@ You can combine multiple source code blocks before executing ledger and do all kinds of other wonderful things with Babel (and org). -@subsection Org-mode with Babel +@subsection Org mode with Babel Using Babel, it is possible to record financial transactions conveniently in an org file and subsequently generate the financial @@ -4183,7 +4154,7 @@ The easiest, albeit possibly less useful, way in which to use Ledger within an org file is to use a single source block to record all Ledger entries. The following is an example source block: @smallexample -#+srcname: allinone +#+name: allinone #+begin_src ledger 2010/01/01 * Starting balance assets:bank:savings £1300.00 @@ -4233,16 +4204,16 @@ Evaluating the code block again would generate a different report. Having to change the actual directive on the code block and re-evaluate makes it difficult to have more than one view of your transactions and financial state. Eventually, babel will support passing arguments to -#+call evaluations of code blocks but this support is missing +@code{#+call} evaluations of code blocks but this support is missing currently. Instead, we can use the concepts of literary programming, as implemented by the noweb features of babel, to help us. -@subsubheading Multiple Ledger source blocks with noweb +@subsubheading Multiple Ledger source blocks with @command{noweb} -The noweb feature of babel allows us to expand references to other code -blocks within a code block. For Ledger, this can be used to group -transactions according to type, say, and then bring various sets of -transactions together to generate reports. +The @command{noweb} feature of babel allows us to expand references to +other code blocks within a code block. For Ledger, this can be used to +group transactions according to type, say, and then bring various sets +of transactions together to generate reports. Using the same transactions used above, we could consider splitting these into expenses and income, as follows: @@ -4254,7 +4225,7 @@ placed several entries, but we could have had each entry in a separate src block. Note that all code blocks you wish to refer to later must have the :noweb yes babel header argument specified. @smallexample -#+srcname: income +#+name: income #+begin_src ledger :noweb yes 2010/01/01 * Starting balance assets:bank:savings £1300.00 @@ -4279,7 +4250,7 @@ The following entries relate to personal expenses, such as rent and food. Again, these have all been placed in a single src block but could have been done individually. @smallexample -#+srcname: expenses +#+name: expenses #+begin_src ledger :noweb yes 2010/07/23 Rent expenses:rent £500.00 @@ -4294,7 +4265,7 @@ have been done individually. Given the ledger entries defined above in the income and expenses code blocks, we can now refer to these using the noweb expansion directives, -<<name>>. We can now define different code blocks to generate specific +@code{<<name>>}. We can now define different code blocks to generate specific reports for those transactions. Below are two examples, one to generate a balance report and one to generate a register report of all transactions. @@ -4302,11 +4273,11 @@ transactions. The overall balance of your account and expenditure with a breakdown according to category is specified by passing the :cmdline bal argument -to Ledger. This code block can now be evaluated (C-c C-c) and the +to Ledger. This code block can now be evaluated (@code{C-c C-c}) and the results generated by incorporating the transactions referred to by the -<<income>> and <<expenses>>= lines. +@code{<<income>>} and @code{<<expenses>>} lines. @smallexample -#+srcname: balance +#+name: balance #+begin_src ledger :cmdline bal :noweb yes <<income>> <<expenses>> @@ -4319,7 +4290,7 @@ results generated by incorporating the transactions referred to by the @end smallexample If you want a more detailed breakdown of where your money is and where -it has been spent, you can specify the -s flag (i.e. :cmdline -s bal) to +it has been spent, you can specify the @code{-s} flag (i.e. @code{:cmdline -s bal}) to tell Ledger to include sub-accounts in the report. @smallexample @@ -4344,11 +4315,11 @@ tell Ledger to include sub-accounts in the report. You can also generate a monthly register (the reg command) by executing the following src block. This presents a summary of transactions for -each monthly period (the -M argument) with a running total in the final +each monthly period (the @code{-M} argument) with a running total in the final column (which should be 0 at the end if all the entries are correct). @smallexample -#+srcname: monthlyregister +#+name: monthlyregister #+begin_src ledger :cmdline -M reg :noweb yes <<income>> <<expenses>> @@ -4372,7 +4343,7 @@ are increasing (or decreasing!). In this case, the final column will be the running total of the assets in our ledger. @smallexample -#+srcname: monthlyassetsregister +#+name: monthlyassetsregister #+begin_src ledger :cmdline -M reg assets :noweb yes <<income>> <<expenses>> @@ -4392,9 +4363,17 @@ file and manipulated using Babel. However, only simple Ledger features have been illustrated; please refer to the Ledger documentation for examples of more complex operations with a ledger. -@node The pricemap Command, The xml Command, Emacs org mode, Reports in other Formats +@node The pricemap Command, The xml Command, Emacs Org mode, Reports in other Formats @subsection The @code{pricemap} Command +If you have the @code{graphviz} graph visualization package installed, ledger +can generate a graph of the relationship between your various +commodities. The output file is in the ``dot'' format. + +This is probably not very interesting, unless you have many different +commodities valued in terms of each other. For example, multiple +currencies and multiples investments valued in those currencies. + @node The xml Command, prices and pricedb, The pricemap Command, Reports in other Formats @subsection The @code{xml} Command @@ -4414,8 +4393,8 @@ The general format used for Ledger data is: </ledger> @end smallexample -The data stream is enclosed in a @samp{ledger} tag, which contains a -series of one or more transactions. Each @samp{xact} describes the +The data stream is enclosed in a @code{ledger} tag, which contains a +series of one or more transactions. Each @code{xact} describes the transaction and contains a series of one or more postings: @smallexample @@ -4432,19 +4411,19 @@ transaction and contains a series of one or more postings: </xact> @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 posting has -been cleared or not. There is also an @samp{en:pending} tag, for -marking pending postings. The @samp{en:code} and @samp{en:payee} tags +The date format for @code{en:date} is always @code{YYYY/MM/DD}. The +@code{en:cleared} tag is optional, and indicates whether the posting has +been cleared or not. There is also an @code{en:pending} tag, for +marking pending postings. The @code{en:code} and @code{en:payee} tags both contain whatever text the user wishes. After the initial transaction data, there must follow a set of postings -marked with @samp{en:postings}. Typically these postings will all +marked with @code{en:postings}. Typically these postings will all balance each other, but if not they will be automatically balanced into -an account named @samp{<Unknown>}. +an account named @code{<Unknown>}. -Within the @samp{en:postings} tag is a series of one or more -@samp{posting}'s, which have the following form: +Within the @code{en:postings} tag is a series of one or more +@code{posting}'s, which have the following form: @smallexample <posting> @@ -4461,13 +4440,13 @@ Within the @samp{en:postings} tag is a series of one or more @end smallexample This is a basic posting. It may also be begin with -@samp{tr:virtual} and/or @samp{tr:generated} tags, to indicate virtual -and auto-generated postings. Then follows the @samp{tr:account} +@code{tr:virtual} and/or @code{tr:generated} tags, to indicate virtual +and auto-generated postings. Then follows the @code{tr:account} tag, which contains the full name of the account the posting is related to. Colons separate parent from child in an account name. Lastly follows the amount of the posting, indicated by -@samp{tr:amount}. Within this tag is a @samp{value} tag, of which +@code{tr:amount}. Within this tag is a @code{value} tag, of which there are four different kinds, each with its own format: @enumerate @@ -4477,15 +4456,15 @@ there are four different kinds, each with its own format: @item balance @end enumerate -The format of a Boolean value is @samp{true} or @samp{false} -surrounded by a @samp{boolean} tag, for example: +The format of a Boolean value is @code{true} or @code{false} +surrounded by a @code{boolean} tag, for example: @smallexample <boolean>true</boolean> @end smallexample The format of an integer value is the numerical value surrounded by an -@samp{integer} tag, for example: +@code{integer} tag, for example: @smallexample <integer>12036</integer> @@ -4547,11 +4526,11 @@ the same data. @node prices and pricedb, , The xml Command, Reports in other Formats -@subsection prices and pricedb +@subsection @code{prices} and @code{pricedb} 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 +commodities. The @code{-A} flag is useful with this report, to +display the running average price, or @code{-D} to show each price's deviation from that average. There is also a @command{pricedb} command which outputs the same @@ -4560,35 +4539,48 @@ by Ledger. This is useful for generating and tidying up pricedb database files. -@node Reports about your Journals, Developer Commands, Reports in other Formats, Reporting Commands +@node Reports about your Journals, , Reports in other Formats, Reporting Commands @section Reports about your Journals @menu * accounts:: * commodities:: +* tags:: * entry and xact:: * payees:: @end menu @node accounts, commodities, Reports about your Journals, Reports about your Journals -@subsection accounts +@subsection @code{accounts} The @command{accounts} reports all of the accounts in the journal. Following the command with a regular expression will limit the output to -accounts matching the regex. +accounts matching the regex. The output is sorted by name. Using the +@code{--count} option will tell you haw many entries use each account. + +@node commodities, tags, accounts, Reports about your Journals +@subsection @command{commodities} +Report all commodities present in the journals under consideration. The + output is sorted by name. Using the @code{--count} option will tell + you haw many entries use each commodity. -@node commodities, entry and xact, accounts, Reports about your Journals -@subsection commodities -Report all commodities present in the journals under consideration. +@node tags, entry and xact, commodities, Reports about your Journals +@subsection @command{tags} +The @command{tags} reports all of the tags in the journal. The output +is sorted by name. Using the @code{--count} option will tell you haw +many entries use each tag. Using the @code{--values} option will report +the values used by each tag. -@node entry and xact, payees, commodities, Reports about your Journals -@subsection entry and xact -The @code{entry} and @command{xact} commands simplify the creation of -new transactions. It works on the principle that 80% of all postings -are variants of earlier postings. Here's how it works: + +@node entry and xact, payees, tags, Reports about your Journals +@subsection @command{draft}, @command{entry} and @command{xact} + +The @code{draft}, @code{entry} and @command{xact} commands simplify the +creation of new transactions. It works on the principle that 80% of all +postings are variants of earlier postings. Here's how it works: Say you currently have this posting in your ledger file: @@ -4599,7 +4591,7 @@ Say you currently have this posting in your ledger file: Liabilities:MasterCard $-15.00 @end smallexample -Now it's @samp{2004/4/9}, and you've just eating at @samp{Viva +Now it's @code{2004/4/9}, and you've just eating at @code{Viva Italiano} again. The exact amounts are different, but the overall form is the same. With the @command{xact} command you can type: @@ -4617,10 +4609,10 @@ This produces the following output: @end smallexample It works by finding a past posting matching the regular expression -@samp{viva}, and assuming that any accounts or amounts specified will +@code{viva}, and assuming that any accounts or amounts specified will be similar to that earlier posting. If Ledger does not succeed in generating a new transaction, an error is printed and the exit code is set -to @samp{1}. +to @code{1}. Here are a few more examples of the @command{xact} command, assuming the above journal transaction: @@ -4638,7 +4630,7 @@ ledger xact 4/9 viva dining "DM 11.50" backwards compatibility with Ledger 2.X. @node payees, , entry and xact, Reports about your Journals -@subsection payees +@subsection @code{payees} The @command{payees} reports all of the unique payees in the journal. To filter the payees displayed you must use the prefix: @smallexample @@ -4652,191 +4644,6 @@ macbook-2:$ -@node Developer Commands, , Reports about your Journals, Reporting Commands -@section Developer Commands -@menu -* echo:: -* reload:: -* source:: -* Debug Options:: -* Pre-commands:: -@end menu - -@node echo, reload, Developer Commands, Developer Commands -@subsection echo -This command simply echos its argument back to the output. - - -@node reload, source, echo, Developer Commands -@subsection reload -Forces ledger to reload any journal files. This function exists to -support external programs controlling a running ledger process and does -nothing for a command line user. - -@node source, Debug Options, reload, Developer Commands -@subsection source -The @code{source} command take a journal file as an argument and parses -it checking for errors, no other reports are generated, and no other -arguments are necessary. Ledger will return success if no errors are -found. - -@node Debug Options, Pre-commands, source, Developer Commands -@subsection Debug Options - -These options are primarily for Ledger developers, but may be of some -use to a user trying something new. - - @option{--args-only} ignore init -files and environment variables for the ledger run. - -@option{--verify} enable additional assertions during run-time. This -causes a significant slowdown. When combined with @option{--debug} -ledger will produce memory trace information. - -@option{--debug "argument"} If Ledger has been built with debug options -this will provide extra data during the run. The following are the -available arguments to debug: - -@multitable @columnfractions .32 .43 .27 -@item @code{account.display} @tab @code{expr.calc.when} @tab @code{org.next_amount} -@item @code{accounts.sorted} @tab @code{expr.compile} @tab @code{org.next_total} -@item @code{amount.convert} @tab @code{filters.changed_value} @tab @code{parser.error} -@item @code{amount.is_zero} @tab @code{filters.changed_value.rounding} @tab @code{pool.commodities} -@item @code{amount.parse} @tab @code{filters.collapse} @tab @code{post.assign} -@item @code{amount.price} @tab @code{filters.forecast} @tab @code{python.init} -@item @code{amount.truncate} @tab @code{filters.revalued} @tab @code{python.interp} -@item @code{amount.unround} @tab @code{format.abbrev} @tab @code{query.mask} -@item @code{amounts.commodities} @tab @code{format.expr} @tab @code{report.predicate} -@item @code{amounts.refs} @tab @code{generate.post} @tab @code{scope.symbols} -@item @code{archive.journal} @tab @code{generate.post.string} @tab @code{textual.include} -@item @code{auto.columns} @tab @code{item.meta} @tab @code{textual.parse} -@item @code{budget.generate} @tab @code{ledger.read} @tab @code{timelog} -@item @code{commodity.annotated.strip} @tab @code{ledger.validate} @tab @code{times.epoch} -@item @code{commodity.annotations} @tab @code{lookup} @tab @code{times.interval} -@item @code{commodity.compare} @tab @code{lookup.account} @tab @code{times.parse} -@item @code{commodity.download} @tab @code{mask.match} @tab @code{value.sort} -@item @code{commodity.prices.add} @tab @code{memory.counts} @tab @code{value.storage.refcount} -@item @code{commodity.prices.find} @tab @code{memory.counts.live} @tab @code{xact.extend} -@item @code{convert.csv} @tab @code{memory.debug} @tab @code{xact.extend.cleared} -@item @code{csv.mappings} @tab @code{op.cons} @tab @code{xact.extend.fail} -@item @code{csv.parse} @tab @code{op.memory} @tab @code{xact.finalize} -@item @code{draft.xact} @tab @code{option.args} -@item @code{expr.calc} @tab @code{option.names} -@end multitable - -@option{--trace INTEGER_TRACE_LEVEL} -Enable tracing. The integer specifies the level of trace desired: -@multitable @columnfractions .3 .7 -@item @code{LOG_OFF} @tab 0 -@item @code{LOG_CRIT} @tab 1 -@item @code{LOG_FATAL} @tab 2 -@item @code{LOG_ASSERT} @tab 3 -@item @code{LOG_ERROR} @tab 4 -@item @code{LOG_VERIFY} @tab 5 -@item @code{LOG_WARN} @tab 6 -@item @code{LOG_INFO} @tab 7 -@item @code{LOG_EXCEPT} @tab 8 -@item @code{LOG_DEBUG} @tab 9 -@item @code{LOG_TRACE} @tab 10 -@item @code{LOG_ALL} @tab 11 -@end multitable - -@option{--verbose} -Print detailed information on the execution of Ledger. - -@option{--version} -Print version information and exit. - -@node Pre-commands, , Debug Options, Developer Commands -@subsection Pre-Commands -Pre-commands are useful when you aren't sure how a command or option -will work. -@table @code -@item args -evaluate the given arguments against the following model transaction: -@smallexample -2004/05/27 Book Store - ; This note applies to all postings. :SecondTag: - Expenses:Books 20 BOOK @@ $10 - ; Metadata: Some Value - ; Typed:: $100 + $200 - ; :ExampleTag: - ; Here follows a note describing the posting. - Liabilities:MasterCard $-200.00 -@end smallexample -@item eval -evaluate the given value expression against the model transaction -@item expr "LIMIT EXPRESSION" -Print details of how ledger parses the given limit expression and apply -it against a model transaction. -@item format "FORMATTING" -Print details of how ledger uses the given formatting description and -apply it against a model transaction. -@item generate -@item parse <VALUE EXPR> -Print details of how ledger uses the given value expression description -and apply it against a model transaction. -@item period -evaluate the given period and report how Ledger interprets it: -@smallexample -20:22:21 ~/ledger (next)> ledger period "this year" ---- Period expression tokens --- -TOK_THIS: this -TOK_YEAR: year -END_REACHED: <EOF> - ---- Before stabilization --- - range: in year 2011 - ---- After stabilization --- - range: in year 2011 - start: 11-Jan-01 - finish: 12-Jan-01 - ---- Sample dates in range (max. 20) --- - 1: 11-Jan-01 -@end smallexample -@item query -evaluate the given query and report how Ledger interprets it against the -model transaction: - -@smallexample -20:25:42 ~/ledger (next)> ledger query "/Book/" ---- Input arguments --- -("/Book/") - ---- Context is first posting of the following transaction --- -2004/05/27 Book Store - ; This note applies to all postings. :SecondTag: - Expenses:Books 20 BOOK @ $10 - ; Metadata: Some Value - ; Typed:: $100 + $200 - ; :ExampleTag: - ; Here follows a note describing the posting. - Liabilities:MasterCard $-200.00 - ---- Input expression --- -(account =~ /Book/) - ---- Text as parsed --- -(account =~ /Book/) - ---- Expression tree --- -0x7fd639c0da40 O_MATCH (1) -0x7fd639c10170 IDENT: account (1) -0x7fd639c10780 VALUE: /Book/ (1) - ---- Compiled tree --- -0x7fd639c10520 O_MATCH (1) -0x7fd639c0d6c0 IDENT: account (1) -0x7fd639c0d680 FUNCTION (1) -0x7fd639c10780 VALUE: /Book/ (1) - ---- Calculated value --- -true -@end smallexample -@item template -@end table @node Command-line Syntax, Budgeting and Forecasting, Reporting Commands, Top @chapter Command-line Syntax @@ -4844,11 +4651,12 @@ true @menu * Basic Usage:: +* Command Line Quick Reference:: * Detailed Options Description:: * Period Expressions:: @end menu -@node Basic Usage, Detailed Options Description, Command-line Syntax, Command-line Syntax +@node Basic Usage, Command Line Quick Reference, Command-line Syntax, Command-line Syntax @section Basic Usage This chapter describes Ledger's features and options. You may wish to @@ -4872,7 +4680,7 @@ meaning, described below. The regular expressions arguments always match the account name that a posting refers to. To match on the payee of the transaction instead, -precede the regular expression with @samp{payee} or @@. For example, the +precede the regular expression with @code{payee} or @@. For example, the following balance command reports account totals for rent, food and movies, but only those whose payee matches Freddie: @@ -4889,8 +4697,148 @@ There are many, many command options available with the However, none of them are required to use the basic reporting commands. +@node Command Line Quick Reference, Detailed Options Description, Basic Usage, Command-line Syntax +@section Command Line Quick Reference + +@menu +* Reporting Commands Quick Reference:: +* Basic Options Quick Reference:: +* Report Filtering Quick Reference:: +* Error Checking and Calculation Options:: +* Output Customization Quick Reference:: +* Grouping Options:: +* Commodity Reporting Quick Reference:: +@end menu + +@node Reporting Commands Quick Reference, Basic Options Quick Reference, Command Line Quick Reference, Command Line Quick Reference +@subsection Reporting Commands +@multitable @columnfractions .2 .8 +@item @strong{Report} @tab @strong{Description} +@item @code{balance} @tab Show account balances +@item @code{register} @tab Show all transactions with running total +@item @code{csv} @tab Show transactions in csv format, for exporting to other programs +@item @code{print} @tab Print transaction in a ledger readable format +@item @code{xml} @tab Produce XML output of the register command +@item @code{emacs} @tab Produce Emacs lisp output +@item @code{equity} @tab Print account balances as transactions +@item @code{prices} @tab Print price history for matching commodities +@item @code{pricedb} @tab Print price history for matching commodities in ledger readable format +@item @code{xact} @tab Used to generate transactions based on previous postings +@end multitable + +@node Basic Options Quick Reference, Report Filtering Quick Reference, Reporting Commands Quick Reference, Command Line Quick Reference +@subsection Basic Options +@multitable @columnfractions .1 .25 .65 +@item @strong{Short} @tab @strong{Long} @tab @strong{Description} +@item @code{-h} @tab @code{--help} @tab prints summary of all options +@item @code{-v} @tab @code{--version} @tab prints version of ledger executable +@item @code{-f FILE} @tab @code{--file FILE} @tab read @file{FILE} as a ledger file +@item @code{-o FILE} @tab @code{--output FILE} @tab redirects output to @file{FILE} +@item @code{-i FILE} @tab @code{--init-file FILE} @tab specify options file +@item @code{-a NAME} @tab @code{--account NAME} @tab specify default account name for QIF file postings +@end multitable + +@node Report Filtering Quick Reference, Error Checking and Calculation Options, Basic Options Quick Reference, Command Line Quick Reference +@subsection Report Filtering +@multitable @columnfractions .1 .25 .65 +@item @strong{Short} @tab @strong{Long} @tab @strong{Description} +@item @code{-c} @tab @code{--current} @tab Display transaction on or before the current date +@item @code{-b DATE} @tab @code{--begin DATE} @tab Begin reports on or after @code{DATE} +@item @code{-e DATE} @tab @code{--end DATE} @tab Limits end date of transactions for report +@item @code{-p STR} @tab @code{--period} @tab Set report period to STR +@item @code{ } @tab @code{--period-sort} @tab Sort postings within each period +@item @code{-C} @tab @code{--cleared} @tab Display only cleared postings +@item @code{} @tab @code{--dc} @tab Display register or balance in debit/credit format +@item @code{-U} @tab @code{--uncleared} @tab Display only uncleared postings +@item @code{-R} @tab @code{--real} @tab Display only real postings +@item @code{-L} @tab @code{--actual} @tab Displays only actual postings, not automated +@item @code{-r} @tab @code{--related} @tab Display related postings +@item @code{} @tab @code{--budget} @tab Display how close your postings meet your budget +@item @code{} @tab @code{--add-budget} @tab Shows un-budgeted postings +@item @code{} @tab @code{--unbudgeted} @tab Shows only un-budgeted postings +@item @code{} @tab @code{--forecast} @tab Project balances into the future +@item @code{-l EXPR} @tab @code{--limit EXPR} @tab Limits postings in calculations +@item @code{-t EXPR} @tab @code{--amount} @tab Change value expression reported in register report +@item @code{-T EXPR} @tab @code{--total} @tab Change the value expression used for ``totals'' column in register and balance reports +@end multitable + +@node Error Checking and Calculation Options, Output Customization Quick Reference, Report Filtering Quick Reference, Command Line Quick Reference +@subsection Error Checking and Calculation Options + +@multitable @columnfractions .1 .25 .65 +@item @strong{Short} @tab @strong{Long} @tab @strong{Description} +@item @code{} @tab @code{--strict} @tab accounts, tags or commodities not previously declared will cause warnings +@item @code{} @tab @code{--pedantic} @tab accounts, tags or commodities not previously declared will cause errors +@item @code{} @tab @code{--check-payees} @tab enable strict and pedantic checking for payees as well as accounts, commodities and tags. +@item @code{} @tab @code{--immediate} @tab instructs ledger to evaluate calculations immediately rather than lazily +@end multitable + + +@node Output Customization Quick Reference, Grouping Options, Error Checking and Calculation Options, Command Line Quick Reference +@subsection Output Customization +@multitable @columnfractions .15 .4 .45 +@item @strong{Short} @tab @strong{Long} @tab @strong{Description} +@item @code{-n} @tab @code{--collapse} @tab Collapse transactions with multiple postings +@item @code{-s} @tab @code{--subtotal} @tab Report register as a single subtotal +@item @code{-P} @tab @code{--by-payee} @tab Report subtotals by payee +@item @code{-E} @tab @code{--empty} @tab Include empty accounts in report +@item @code{-W} @tab @code{--weekly} @tab Report posting totals by week +@item @code{-Y} @tab @code{--yearly} @tab Report posting totals by year +@item @code{} @tab @code{--dow} @tab report Posting totals by day of week +@item @code{-S EXPR} @tab @code{--sort EXPR} @tab Sorts a report using @code{EXPR} +@item @code{-w} @tab @code{--wide} @tab Assume 132 columns instead of 80 +@item @code{} @tab @code{--head N} @tab Report the first N postings +@item @code{} @tab @code{--tail N} @tab Report the last N postings +@item @code{} @tab @code{--pager PATH} @tab Direct output to @code{PATH} pager program +@item @code{-A} @tab @code{--average} @tab Reports average posting value +@item @code{-D} @tab @code{--deviation} @tab Reports each posting deviation from the average +@item @code{-%} @tab @code{--percent} @tab Show subtotals in the balance report as percentages +@c @item @code{} @tab @code{--totals} @tab Include running total in the @code{xml} report +@item @code{} @tab @code{--pivot TAG} @tab produce a pivot table of the tag type specified +@item @code{-j} @tab @code{--amount-data} @tab Show only date and value column to format the output for plots +@item @tab @code{--plot-amount-format STR} @tab specify the format for the plot output +@item @code{-J} @tab @code{--total-data} @tab Show only dates and totals to format the output for plots +@item @tab @code{--plot-total-format STR} @tab specify the format for the plot output +@item @code{-d EXPR} @tab @code{--display EXPR} @tab Display only posting that meet the criteris in the EXPR +@item @code{-y STR} @tab @code{--date-format STR} @tab Change the basic date format used in reports +@item @code{-F STR} @tab @code{--format STR} @tab Set reporting format +@item @code{} @tab @code{--balance-format STR} @tab +@item @code{} @tab @code{--register-format STR} @tab +@item @code{} @tab @code{--prices-format STR} @tab +@item @code{-w register} @tab @code{--wide-register-format STR} @tab +@item @code{} @tab @code{--anon} @tab Print the ledger register with anonymized accounts and payees, useful for filing bug reports +@end multitable + +@node Grouping Options, Commodity Reporting Quick Reference, Output Customization Quick Reference, Command Line Quick Reference +@subsection Grouping Options +@multitable @columnfractions .1 .25 .65 +@item @strong{Short} @tab @strong{Long} @tab @strong{Description} +@item @code{-P} @tab @code{--by-payee} @tab Group postings by common payee names +@item @code{-D} @tab @code{--daily} @tab Group postings by day +@item @code{-W} @tab @code{--weekly} @tab Group postings by week +@item @code{-M} @tab @code{--monthly} @tab Group postings by month +@item @code{} @tab @code{--quarterly} @tab Group postings by quarter +@item @code{-Y} @tab @code{--yearly} @tab Group postings by year +@item @code{} @tab @code{--dow} @tab Group by day of weeks +@item @code{-s} @tab @code{--subtotal} @tab Group posting together, similar to balance report +@end multitable + +@node Commodity Reporting Quick Reference, , Grouping Options, Command Line Quick Reference +@subsection Commodity Reporting -@node Detailed Options Description, Period Expressions, Basic Usage, Command-line Syntax +@multitable @columnfractions .1 .25 .65 +@item @strong{Short} @tab @strong{Long} @tab @strong{Description} +@item @code{} @tab @code{--price-db FILE} @tab Use @file{FILE} for retrieving stored commodity prices +@item @code{-L MINS} @tab @code{--price-exp MINS} @tab Set expected freshness of prices in minutes +@item @code{-Q} @tab @code{--download} @tab Download quotes using @code{getquote} +@item @code{} @tab @code{--getquote} @tab Sets path to a user defined script to download commodity prices. +@item @code{-O} @tab @code{--quantity} @tab Report commodity totals without conversion +@item @code{-B} @tab @code{--basis} @tab Report cost basis +@item @code{-V} @tab @code{--market} @tab Report last known market value +@item @code{-G} @tab @code{--gain} @tab Report net gain loss for commodities that have a price history +@end multitable + +@node Detailed Options Description, Period Expressions, Command Line Quick Reference, Command-line Syntax @section Detailed Option Description @menu @@ -4898,7 +4846,6 @@ commands. * Session Options:: * Report Options:: * Report Filtering:: -* Search Terms:: * Output Customization:: * Commodity Reporting:: * Environment Variables:: @@ -4914,58 +4861,60 @@ GUIs, which would make use of the different scopes by keeping an instance of Ledger running in the background and running multiple sessions with multiple reports per session. - -@option{--args-only} Ignore all environment and init-file settings and +@table @code +@item --args-only +Ignore all environment and init-file settings and use only command-line arguments to control Ledger. Useful for debugs or testing small Journal files not associated with you main financial database. - -@option{--help} +@item --help Displays the info page for ledger. -@option{--init-file <PATH>} -Specifies the location of the init file @file{.ledgerrc} +@item --init-file <PATH> +Specifies the location of the init file. The default is @file{~/.ledgerrc} -@option{--options} Display the options in effect for this Ledger -invocation, along with their values and the source of those values, for -example: +@item --options + Display the options in effect for this Ledger invocation, along with +their values and the source of those values, for example: @smallexample 14:15:02 > ledger --options bal --cleared -f ~/ledger/test/input/drewr3.dat =============================================================================== [Global scope options] [Session scope options] - --file = ~/ledger/test/input/drewr3.dat -f - --price-db = ~/FinanceData/PriceDB $price-db + --file = ~/ledger/test/input/drewr3.dat -f + --price-db = ~/FinanceData/PriceDB $price-db [Report scope options] - --cleared --cleared - --color ?normalize - --date-format = %Y/%m/%d $date-format - --limit = cleared --cleared - --prepend-width = 0 ?normalize - --meta-width = 0 ?normalize - --date-width = 10 ?normalize - --payee-width = 21 ?normalize - --account-width = 21 ?normalize - --amount-width = 12 ?normalize - --total-width = 12 ?normalize + --cleared --cleared + --color ?normalize + --date-format = %Y/%m/%d $date-format + --limit = cleared --cleared + --prepend-width = 0 ?normalize + --meta-width = 0 ?normalize + --date-width = 10 ?normalize + --payee-width = 21 ?normalize + --account-width = 21 ?normalize + --amount-width = 12 ?normalize + --total-width = 12 ?normalize =============================================================================== $ 775.00 Assets:Checking $ -1,000.00 Equity:Opening Balances $ 225.00 Expenses:Food:Groceries -------------------- 0 - @end smallexample -@noindent For the `source' column, a value starting with a `@code{-}' or -`@code{--}' indicated the source was a command line argument. It the -entry starts with a `@code{$}', the source was an environment -variable. If the source is `@code{?normalize}' the value was set +@noindent For the source column, a value starting with a @code{-} or +@code{--} indicated the source was a command line argument. It the +entry starts with a @code{$}, the source was an environment +variable. If the source is @code{?normalize} the value was set internally by ledger, in a function called @code{normalize_options}. -@option{--script <PATH>} Execute a ledger script. +@item --script <PATH> +Execute a ledger script. +@end table + @node Session Options, Report Options, Global Options, Detailed Options Description @subsection Session Options @@ -4976,22 +4925,26 @@ GUIs, which would make use of the different scopes by keeping an instance of Ledger running in the background and running multiple sessions with multiple reports per session. -@option{--decimal-comma} Direct Ledger to parse journals using the -European standard comma as decimal separator, vice a period. +@table @code +@item --decimal-comma +Direct Ledger to parse journals using the European standard comma as +decimal separator, vice a period. -@option{--download} Direct Ledger to download prices using the script -defined in @code{--getquote}. +@item --download +Direct Ledger to download prices using the script defined in +@code{--getquote}. -@option{--file <PATH>} +@item --file <PATH> Specify the input file path for this invocation. @cindex getquote @cindex download prices -@option{--getquote <PATH>} Tells ledger where to find the user defined -script to download prices information. +@item --getquote <PATH> +Tells ledger where to find the user defined script to download prices +information. -@option{--input-date-format <DATE-FORMAT>} Specify the input date format -for journal entries. For example, +@item --input-date-format <DATE-FORMAT> +Specify the input date format for journal entries. For example, @smallexample ledger convert Export.csv --input-date-format "%m/%d/%Y" @end smallexample @@ -5000,8 +4953,8 @@ Would convert the @file{Export.csv} file to ledger format, assuming the the dates in the CSV file are like 12/23/2009 (@pxref{Date and Time Format Codes}). -@option{--master-account <ARGUMENT>} Prepends all account names with the -argument. +@item --master-account <STRING> +Prepends all account names with the argument. @smallexample 21:51:39 ~/ledger (next)> ledger -f test/input/drewr3.dat bal --master-account HUMBUG 0 HUMBUG @@ -5026,23 +4979,24 @@ argument. $ 200.00 Mortgage:Principal @end smallexample -@option{--price-db <PATH>} Specify the location of the price entry data -file. - -@option{--price-exp INTEGER_MINUTES} Set 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. +@item --price-db <PATH> +Specify the location of the price entry data file. -@option{--strict} Ledger normally silently accepts any account or -commodity in a posting, even if you have misspelled a common used one. -The option @code{--strict} changes that behavior. While running -@code{--strict}, Ledger interprets all cleared transactions as correct, -and if it finds a new account or commodity (same as a misspelled -commodity or account) it will issue a warning giving you the file and -line number of the problem. +@item --price-exp INTEGER_MINUTES +Set 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 +@code{--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. +@item --strict +Ledger normally silently accepts any account or commodity in a posting, +even if you have misspelled a common used one. The option +@code{--strict} changes that behavior. While running @code{--strict}, +Ledger interprets all cleared transactions as correct, and if it finds a +new account or commodity (same as a misspelled commodity or account) it +will issue a warning giving you the file and line number of the problem. +@end table @node Report Options, Report Filtering, Session Options, Detailed Options Description @subsection Report Options Options for Ledger report affect three separate scopes of operation: @@ -5051,48 +5005,61 @@ difference between these scopes. Ledger 3.0 contains provisions for GUIs, which would make use of the different scopes by keeping an instance of Ledger running in the background and running multiple sessions with multiple reports per session. -@option{--abbrev-len <INT>} Sets the minimum + +@table @code +@item --abbrev-len <INT> +Sets the minimum length an account can be abbreviated to if it doesn't fit inside the @code{account-width}. If @code{abbrev-len} is zero, then the account name will be truncated on the right. If @code{abbrev-len} is greater than @code{account-width} then the account will be truncated on the left, with no shortening of the account names in order to fit into the desired width. - -@option{--account <STR>} Prepend @code{<STR>} to all accounts +@item --account <STR> +Prepend @code{<STR>} to all accounts reported. That is, the option @code{--account Personal} would tack @code{Personal:} to the beginning of every account reported in a balance report or register report. -@option{--account-width <INT>} Set the width of the account column in +@item --account-width <INT> + Set the width of the account column in the @code{register} report to @code{N} characters. -@option{--actual-dates} Show actual dates of transactions +@item --actual-dates + Show actual dates of transactions (@pxref{Effective Dates}). Also @code{-L}. -@option{--actual} Report only real transactions, with no automated or +@item --actual + Report only real transactions, with no automated or virtual transactions used. -@option{--add-budget} Show only unbudgeted postings. +@item --add-budget + Show only unbudgeted postings. -@option{--amount-data} On a register report print only the dates and +@item --amount-data + On a register report print only the dates and amount of postings. Useful for graphing and spreadsheet applications. -@option{--amount <EXPR>} Apply the given value expression to the posting +@item --amount <EXPR> + Apply the given value expression to the posting amount (@pxref{Value Expressions}). Using @code{--amount} you can apply an arbitrary transformation to the postings. -@option{--amount-width <INT>} Set the width in characters of the amount +@item --amount-width <INT> + Set the width in characters of the amount column in the register report. -@option{--anon} anonymizes registry output, mostly for sending in bug +@item --anon + anonymizes registry output, mostly for sending in bug reports. -@option{--average} Print average values over the number of transactions +@item --average + Print average values over the number of transactions instead of running totals. -@option{--balance-format <STR>} specifies the format to use for the +@item --balance-format <STR> + specifies the format to use for the @code{balance} report (@pxref{Format Strings}). The default is: @smallexample "%(justify(scrub(display_total), 20, -1, true, color))" @@ -5102,14 +5069,18 @@ instead of running totals. "--------------------\n" @end smallexample -@option{--base} ASK JOHN +@item --base + ASK JOHN -@option{--basis} Report the cost basis on all posting +@item --basis + Report the cost basis on all posting -@option{--begin <DATE>} Specify the start date of all calculations. +@item --begin <DATE> + Specify the start date of all calculations. Transactions before that date will be ignored. -@option{--bold-if <EXPR>} print the entire line in bold if the given +@item --bold-if <EXPR> + print the entire line in bold if the given value expression is true (@pxref{Value Expressions}). @smallexample @@ -5117,7 +5088,8 @@ ledger reg Expenses --begin Dec --bold-if "amount > 100" @end smallexample @noindent list all transactions since the beginning of December and bold any posting greater than $100 -@option{--budget-format <FORMAT_STRING>} +@item --budget-format <FORMAT_STRING> + specifies the format to use for the @code{budget} report (@pxref{Format Strings}). The default is: @smallexample "%(justify(scrub(display_total), 20, -1, true, color))" @@ -5127,14 +5099,17 @@ specifies the format to use for the @code{budget} report (@pxref{Format Strings} "--------------------\n" @end smallexample -@option{--budget} only display budgeted items. In a register report this +@item --budget + only display budgeted items. In a register report this displays transaction in the budget, in a balance report this displays accounts in the budget (@pxref{Budgeting and Forecasting}). -@option{--by-payee <REGEXP>} +@item --by-payee <REGEXP> + group the register report by payee. -@option{--cleared-format <FORMAT_STRING>} specifies the format to use +@item --cleared-format <FORMAT_STRING> + specifies the format to use for the @code{cleared} report (@pxref{Format Strings}). The default is: @smallexample @@ -5149,25 +5124,32 @@ for the @code{cleared} report (@pxref{Format Strings}). The default is: "---------------- ---------------- ---------\n" @end smallexample -@option{--cleared} consider only transaction that have been cleared for +@item --cleared + consider only transaction that have been cleared for display and calculation. -@option{--collapse} By default ledger prints all account in an account +@item --collapse + By default ledger prints all account in an account tree. With @code{--collapse} it print only the top level account specified. -@option{--collapse-if-zero} Collapses the account display only if it has +@item --collapse-if-zero + Collapses the account display only if it has a zero balance. -@option{--color} use color is the tty supports it. +@item --color + use color is the tty supports it. -@option{--columns <INT>} specify the width of the register report in +@item --columns <INT> + specify the width of the register report in characters. -@option{--count} Direct ledger to report the number of items when +@item --count + Direct ledger to report the number of items when appended to the commodities, accounts or payees command. -@option{--csv-format} specifies the format to use for the @code{csv} +@item --csv-format + specifies the format to use for the @code{csv} report (@pxref{Format Strings}). The default is: @smallexample "%(quoted(date))," @@ -5179,26 +5161,33 @@ report (@pxref{Format Strings}). The default is: "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," "%(quoted(join(note | xact.note)))\n" @end smallexample -@option{--current} +@item --current + Shorthand for @code{--limit "date <= today"} -@option{--daily} +@item --daily + Shorthand for @code{--period "daily"} -@option{--date-format <DATE-FORMAT>} specifies format ledger should use +@item --date-format <DATE-FORMAT> + specifies format ledger should use to print dates (@pxref{Date and Time Format Codes}). -@option{--date <EXPR>} transforms the date of the transaction using +@item --date <EXPR> + transforms the date of the transaction using @code{EXPR} -@option{--date-width <INT>} specifies the width, in characters, of the +@item --date-width <INT> + specifies the width, in characters, of the date column in the register report. -@option{--datetime-format} +@item --datetime-format + ASK JOHN -@option{--dc} Display register or balance in debit/credit format -If you use @samp{--dc} with either the register (reg) or balance (bal) commands, you +@item --dc + Display register or balance in debit/credit format +If you use @code{--dc} with either the register (reg) or balance (bal) commands, you will now get extra columns. The register goes from this: @smallexample 12-Mar-10 Employer Assets:Cash $100 $100 @@ -5227,7 +5216,7 @@ will now get extra columns. The register goes from this: @noindent Where the first column is debits, the second is credits, and the third is the running total. Only the running total may contain negative values. -For the balance report without @samp{--dc}: +For the balance report without @code{--dc}: @smallexample $70 Assets:Cash @@ -5237,7 +5226,7 @@ For the balance report without @samp{--dc}: 0 @end smallexample -@noindent And with @samp{--dc} it becomes this: +@noindent And with @code{--dc} it becomes this: @smallexample $105 $35 $70 Assets:Cash @@ -5248,81 +5237,102 @@ For the balance report without @samp{--dc}: @end smallexample -@option{--depth <INT>} limit the depth of the account tree. In a balance -report, for example, a @code{--depth 2} statement will print balances -only for account with two levels, i.e. @code{Expenses:Entertainment} but -not @code{Expenses:entertainemnt:Dining}. This is a display predicate, -which means it only affects display, not the total calculations. +@item --depth <INT> + limit the depth of the account tree. In a balance report, for example, +a @code{--depth 2} statement will print balances only for account with +two levels, i.e. @code{Expenses:Entertainment} but not +@code{Expenses:entertainemnt:Dining}. This is a display predicate, which +means it only affects display, not the total calculations. -@option{--deviation} reports each posting’s deviation from the - average. It is only mean- ingful in the register and prices reports. +@item --deviation + reports each posting’s deviation from the average. It is only + meaningful in the register and prices reports. -@option{--display-amount <EXPR>} apply a transform to the +@item --display-amount <EXPR> + apply a transform to the @strong{displayed} amount. This occurs after calculations occur. -@option{--display <BOOLEAN_EXPR>} +@item --display <BOOLEAN_EXPR> + display lines that satisfy the expression given. -@option{--display-total <EXPR>} apply a transform to the +@item --display-total <EXPR> + apply a transform to the @strong{displayed} total. This occurs after calculations occur. -@option{--dow} +@item --dow + group transactions by the day of the week. @smallexample ledger reg Expenses --dow --collapse @end smallexample @noindent will print all Expenses totalled for each day of the week. -@option{--effective} +@item --effective + use effective dates for all calculations (@pxref{Effective Dates}). -@option{--empty} +@item --empty + include empty accounts in the report. -@option{--end <DATE>} +@item --end <DATE> + specify the end date for transaction to be considered in the report. -@option{--equity} related to the @code{equity} command (@pxref{The +@item --equity + related to the @code{equity} command (@pxref{The equity Command}). Gives current account balances in the form of a register report. -@option{--exact} +@item --exact + ASK JOHN -@option{--exchange <COMMODITY>} display values in terms of the given +@item --exchange <COMMODITY> + display values in terms of the given commodity. The latest available price is used. -@option{--flat} force the full names of accounts to be used inthe +@item --flat + force the full names of accounts to be used in the balance report. The balance report will not use an indented tree. -@option{--force-color} output tty color codes even if the tty doesn't -support them. Ueful for TTY that don't advertise their capabilities +@item --force-color + output tty color codes even if the tty doesn't +support them. Useful for TTY that don't advertise their capabilities correctly. -@option{--force-pager} +@item --force-pager + force Ledger to paginate its output. -@option{forecast-while} -FIX THIS ENTRY +@item --forecast-while <VEXPR> +Continue forecasting while @code{<VEXPR>} is true. -@option{forecast-years} -FIX THIS ENTRY +@item --forecast-years <INT> +Forecast at most @code{N} years in the future. + +@item --format <FORMAT STRING> -@option{--format <FORMAT STRING>} use the given format string to print output. -@option{--gain} -report on gains using the latest available prices . +@item --gain -@option{generated} -ASK JOHN +report on gains using the latest available prices. + +@item --generated +Include auto-generated postings (such as those from automated transactions) +in the report, in cases where you normally wouldn't want +them. -@option{--group-by <EXPR>} group transaction together in the register -report. EXPR can be anything, although most common would be -@code{"payee"} or @code{"commodity"}. The @code{tags()} function is +@item --group-by <EXPR> + group transaction together in the register +report. @code{EXPR} can be anything, although most common would be +@code{payee} or @code{commodity}. The @code{tags()} function is also useful here. -@option{--group-title-format} sets the format for the headers that +@item --group-title-format + sets the format for the headers that separate reports section of a grouped report. Only has effect with a @code{--group-by} register report. @smallexample @@ -5339,200 +5349,285 @@ ledger reg Expenses --group-by "payee" --group-title-format "------------------- @end smallexample -@option{--head <INT>} -Print the first INT entries. Opposite of @code{--tail}. +@item --head <INT> + +Print the first @code{INT} entries. Opposite of @code{--tail}. + +@item --inject +Use @code{Expected} amounts in calculations. In the case that you know +that amount a transaction should be, but the actual transaction has the +wrong value you can use metadata to put in the expected amount: +@smallexample +2012-03-12 Paycheck + Income $-990; Expected:: $-1000.00 + Checking +@end smallexample -@option{--inject} -See email from John W. +Then using the command @code{ledger reg --inject=Expected Income} would +treat the transaction as if the ``Expected Value'' was actual. +@item --invert -@option{--invert} Change the sign of all reported values. -@option{--limit <EXPR>} Only transactions that satisfy the expression -will be considered in the calculation. +@item --limit <EXPR> + Only transactions that satisfy the expression will be considered in the +calculation. -@option{--lot-dates} -FIX THIS ENTRY +@item --lot-dates +Report the date on which each commodity in a balance report was purchased. -@option{--lot-prices} -FIX THIS ENTRY +@item --lot-prices -@option{--lot-tags} -FIX THIS ENTRY +Report the price at which each commodity in a balance report was purchased. -@option{--lots-actual} -FIX THIS ENTRY +@item --lot-tags -@option{--lots} -FIX THIS ENTRY +Report the tag attached to each commodity in a balance report. -@option{market} -FIX THIS ENTRY +@item --lots-actual -@option{meta} FIX THIS ENTRY -@option{meta-width} -FIX THIS ENTRY +@item --lots -@option{--monthly} -FIX THIS ENTRY +Report the date and price at which each commodity was purchased in a balance report. + +@item --market + +Use the latest market value for all commodities. + +@item --meta <TAG> + +In the register report, prepend the transaction with the value of the given tag. + +@item --meta-width + +Specify the width of the Meta column used for the @code{--meta} options. + +@item --monthly + +synonym for @code{--period "monthly"} + +@item --no-color -@option{--no-color} suppress any color TTY output. -@option{--no-rounding} -FIX THIS ENTRY +@item --no-rounding +Don't output <Rounding> postings. Note that this will cause the running total +to often not add up! It's main use is for @code{-j} and @code{-J} reports. -@option{--no-titles} -FIX THIS ENTRY +@item --no-titles -@option{--no-total} -FIX THIS ENTRY +Suppress the output of group titles -@option{--now} -FIX THIS ENTRY +@item --no-total -@option{only} -FIX THIS ENTRY +Suppress printing the final total line in a balance report. -@option{--output} -FIX THIS ENTRY +@item --now +Define the current date in case to you to do calculate in the past or +future using @code{--current} -@option{--pager} -FIX THIS ENTRY +@item --only -@option{payee} -FIX THIS ENTRY +This is a postings predicate that applies after certain transforms have +been executed, such as periodic gathering. -@option{payee-width} -FIX THIS ENTRY +@item --output <PATH> +Redirect the output of ledger to the file defined in @file{PATH}. -@option{--pending} -Use only postings tht are marked pending +@item --pager -@option{percent} -FIX THIS ENTRY +Specify the pager program to use. -@option{period} -FIX THIS ENTRY +@item --payee <VEXPR> -@option{--pivot} -FIX THIS ENTRY +Sets a value expression for formatting the payee. In the register report +this prevents the second entry from having a date and payee for each +transaction -@option{plot-amount-format} -FIX THIS ENTRY +@item --payee-width N -@option{plot-total-format} -FIX THIS ENTRY +Set the number of columns dedicated to the payee in the register report. -@option{prepend-format} -FIX THIS ENTRY +@item --pending -@option{prepend-width} -FIX THIS ENTRY +Use only postings that are marked pending -@option{pricedb-format} -FIX THIS ENTRY +@item --percent +Calculate the percentage value of each account in a balance reports. +Only works for account that have a single commodity. -@option{price} -FIX THIS ENTRY +@item --period <PERIOD EXPRESSION> -@option{prices-format} -FIX THIS ENTRY +Define a period expression the sets the time period during which +transactions are to be accounted. For a register report only the +transactions that satisfy the period expression with be displayed. For +a balance report only those transactions will be accounted in the final +balances. -@option{quantity} -FIX THIS ENTRY +@item --pivot <VALUED TAG> -@option{--quarterly} -FIX THIS ENTRY +Produce a balance pivot report ``around'' the given tag. For example, +if you have multiple cars and track each fuel purchase in +@code{Expenses:Auto:Fuel} and tag each fuel purchase with a tag +identifying which car the purchase was for @code{; Car: Prius}, then the command: +@smallexample +ledger bal Fuel --pivot "Car" --period "this year" + $ 3491.26 Car + $ 1084.22 M3:Expenses:Auto:Fuel + $ 149.65 MG V11:Expenses:Auto:Fuel + $ 621.89 Prius:Expenses:Auto:Fuel + $ 1635.50 Sienna:Expenses:Auto:Fuel + $ 42.69 Expenses:Auto:Fuel +-------------------- + $ 3533.95 +@end smallexample -@option{raw} -FIX THIS ENTRY +@xref{Metadata values}. +@item --plot-amount-format -@option{--real} Account using only real transactions ignoring virtual -and automatic transactions. +Define the output format for a amount data plot. @xref{Visualizing with Gnuplot}. -@option{register-format} -FIX THIS ENTRY +@item --plot-total-format + +Define the output format for a total data plot. @xref{Visualizing with Gnuplot}. + +@item --prepend-format STR + +Prepend STR to every line of the output + +@item --prepend-width N + +Reserve @code{N} spaces at the beginning of each line of the output -@option{related-all} -FIX THIS ENTRY -@option{--related} -In a register report show the related account. +@item --price +use the price of the commodity purchase for performing calculations + +@item --quantity -@option{--revalued-only} FIX THIS ENTRY -@option{--revalued-total} +@item --quarterly + +Synonym for @code{--period "quarterly"}. + +@item --raw + +In the print report, show transactions using the exact same syntax as +specified by the user in their data file. Don't do any massaging or +interpreting. Can be useful for minor cleanups, like just aligning +amounts. + +@item --real +Account using only real transactions ignoring virtual and automatic +transactions. + + +@item --related-all + +Show all postings in a transaction, similar to @code{--related} but show +both ``sides'' of each transaction. + +@item --related + +In a register report show the related account. This is the other +``side'' of the transaction. + +@item --revalued-only + FIX THIS ENTRY -@option{--revalued} +@item --revalued-total + FIX THIS ENTRY -@option{seed} +@item --revalued + FIX THIS ENTRY -@option{sort-all} +@item --seed + +Sets the random seed for the @code{generate} command. Used as part of development testing. + +@item --sort-all + FIX THIS ENTRY -@option{--sort <VEXPR>} +@item --sort <VEXPR> + Sort the register report based on the value expression given to sort -@option{--sort-xacts} -FIX THIS ENTRY +@item --sort-xacts <VEXPR> -@option{--start-of-week <INT>} Tell ledger to use a particular day of -the week to start its ``weekly'' summary. @code{--start-of-week=1} -specifies Monday as the start of the week. +Sort the posting within transactions using the given value expression -@option{--subtotal} -FIX THIS ENTRY +@item --start-of-week <INT> +Tell ledger to use a particular day of the week to start its ``weekly'' +summary. @code{--start-of-week=1} specifies Monday as the start of the +week. -@option{--tail <INT>} -report only the last <INT> entries. Only useful ona register report. +@item --subtotal -@option{total-data} FIX THIS ENTRY -@option{total} -FIX THIS ENTRY +@item --tail <INT> -@option{total-width} -FIX THIS ENTRY +report only the last @code{INT} entries. Only useful on a register report. -@option{truncate} -FIX THIS ENTRY +@item --total-data -@option{unbudgeted} FIX THIS ENTRY -@option{uncleared} -FIX THIS ENTRY +@item --total <VEXPR> +Define a value expression used to calculate the total in reports. + +@item --total-width +Set the width of the total field in the register report. + +@item --truncate +Indicates how truncation should happen when the contents of columns +exceed their width. Valid arguments are @code{leading}, @code{middle}, +and @code{trailing}. The default is smarter than any of these three, as +it considers sub-names within the account name (that style is called +``abbreviate''). + +@item --unbudgeted -@option{unrealized-gains} FIX THIS ENTRY -@option{unrealized-losses} +@item --uncleared + +Use only uncleared transactions in calculations and reports. + +@item --unrealized-gains + FIX THIS ENTRY -@option{unrealized} +@item --unrealized-losses + FIX THIS ENTRY -@option{unround} +@item --unrealized + FIX THIS ENTRY -@option{--weekly} -synonymn for @code{--period "weekly"} +@item --unround +Perform all calculations without rounding and display results to full precision. + +@item --weekly -@option{--wide} lets the register report use 132 columns. Identical to -@code{--columns "132"} +synonym for @code{--period "weekly"} -@option{yearly} -synonymn for @code{--period "yearly"} +@item --wide +lets the register report use 132 columns. Identical to @code{--columns +"132"} +@item --yearly +synonym for @code{--period "yearly"} +@end table @@ -5542,29 +5637,37 @@ These are the most basic command options. Most likely, the user will want to set them using environment variables (see @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. -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 -postings, but it may contain option settings. To specify options -in the init file, use the same syntax as the command-line, but put each -option on it's own line. Here's an example init file: +@table @code +@item --help +@item -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. + +@item --version +@item -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. + +@item --file FILE +@item -f FILE +reads FILE as a ledger file. This command may be used multiple times. +Typically, the environment variable @env{LEDGER_FILE} is set, rather +than using this command-line option. + +@item --output FILE +@item -o FILE +redirects output from any command to @var{FILE}. By default, all output +goes to standard output. + +@item --init-file FILE +@item -i FILE +causes @code{FILE} to be read by ledger before any other ledger file. This +file may not contain any postings, but it may contain option settings. +To specify options in the init file, use the same syntax as the +command-line, but put each option on it's own line. Here's an example +init file: @smallexample --price-db ~/finance/.pricedb @@ -5576,68 +5679,83 @@ Option settings on the command-line or in the environment always take precedence over settings in the init file. -@option{--account NAME} (@option{-a NAME}) specifies the default -account which QIF file postings are assumed to relate to. +@item --account NAME +@item -a NAME +specifies the default account which QIF file postings are assumed to +relate to. +@end table -@node Report Filtering, Search Terms, Report Options, Detailed Options Description +@node Report Filtering, Output Customization, Report Options, Detailed Options Description @subsection Report filtering These options change which postings affect the outcome of a report, in ways other than just using regular expressions: -@option{--current}(@option{-c}) displays only transactions occurring on or -before the current date. - -@option{--begin DATE} (@option{-b DATE}) constrains the report to -transactions on or after @var{DATE}. Only transactions 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 transaction. (Note: This -is different from using @option{--display} to constrain what is -displayed). - -@option{--end DATE} (@option{-e DATE}) constrains the report so that -transactions 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 transactions within each -period separately, making it easy to see weekly, monthly, quarterly, -etc., posting 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 postings 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: +@table @code +@item --current +@item -c +displays only transactions occurring on or before the current date. + +@item --begin DATE +@item -b DATE +constrains the report to transactions on or after @var{DATE}. Only +transactions 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 transaction. (Note: This is different from using +@code{--display} to constrain what is displayed). + +@item --end DATE +@item -e DATE +constrains the report so that transactions on or after @var{DATE} are +not considered. The ending date is inclusive. + +@item --period STR +@item -p STR +sets the reporting period to @var{STR}. This will subtotal all matching +transactions within each period separately, making it easy to see +weekly, monthly, quarterly, etc., posting 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}. + +@item --period-sort EXPR +sorts the postings 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: @smallexample ledger -M --period-sort -At reg ^Expenses @end smallexample -@option{--cleared} (@option{-C}) displays only postings whose transaction -has been marked ``cleared'' (by placing an asterisk to the right of the -date). +@item --cleared +@item -C + displays only postings whose transaction has been marked ``cleared'' +(by placing an asterisk to the right of the date). -@option{--uncleared} (@option{-U}) displays only postings whose -transaction has not been marked ``cleared'' (i.e., if there is no asterisk to -the right of the date). +@item --uncleared +@item -U +displays only postings whose transaction has not been marked ``cleared'' +(i.e., if there is no asterisk to the right of the date). -@option{--real} (@option{-R}) displays only real postings, not virtual. -(A virtual posting is indicated by surrounding the account name with -parentheses or brackets; see @ref{Virtual postings} for more -information). +@item --real +@item -R + displays only real postings, not virtual. (A virtual posting is +indicated by surrounding the account name with parentheses or brackets; +see @ref{Virtual postings} for more information). -@option{--actual} (@option{-L}) displays only actual postings, and -not those created due to automated postings. +@item --actual +@item -L +displays only actual postings, and not those created due to automated +postings. -@option{--related} (@option{-r}) displays postings that are -related to whichever postings 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 transactions having a related posting. -For example, if a file had this transaction: +@item --related +@item -r +displays postings that are related to whichever postings 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 transactions having a +related posting. For example, if a file had this transaction: @smallexample 2004/03/20 Safeway @@ -5660,154 +5778,184 @@ posting that matched: Assets:Checking $85.00 $65.00 @end smallexample -@option{--budget} is useful for displaying how close your postings -meet your budget. @option{--add-budget} also shows un-budgeted -postings, 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}. +@item --budget +is useful for displaying how close your postings meet your budget. +@code{--add-budget} also shows un-budgeted postings, while +@code{--unbudgeted} shows only those. @code{--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 postings -take part in the calculations of a report. +@item --limit EXPR +@item -l EXPR +limits which postings 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 +@item --amount EXPR +@item -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 Search Terms, Output Customization, Report Filtering, Detailed Options Description -@subsection Search Terms - -Valid Ledger invocations look like: -@smallexample - ledger [OPTIONS] <COMMAND> <SEARCH-TERMS> -@end smallexample - -Where @samp{COMMAND} is any command verb (@pxref{Reporting Commands}), @samp{OPTIONS} can occur -anywhere, and @samp{SEARCH-TERM} is one or more of the following: - -@smallexample - word search for any account containing 'word' - TERM and TERM boolean AND between terms - TERM or TERM boolean OR between terms - not TERM invert the meaning of the term - payee word search for any payee containing 'word' - @@word shorthand for 'payee word' - desc word alternate for 'payee word' - note word search for any note containing 'word' - &word shorthand for 'note word' - tag word search for any metadata tag containing 'word' - tag word=value search for any metadata tag containing 'word' - whose value contains 'value' - %word shorthand for 'tag word' - %word=value shorthand for 'tag word=value' - meta word alternate for 'tag word' - meta word=value alternate for 'tag word=value' - expr 'EXPR' apply the given value expression as a predicate - '=EXPR' shorthand for 'expr EXPR' - \( TERMS \) group terms; useful if using and/or/not -@end smallexample - -So, to list all transaction that charged to ``food'' but not ``dining'' for any payee other than ``chang'' the following three commands would be equivalent: - -@smallexample - ledger reg food not dining @@chang - ledger reg food and not dining and not payee chang - ledger reg food not dining expr 'payee =~ /chang/' -@end smallexample - -@node Output Customization, Commodity Reporting, Search Terms, Detailed Options Description +@item --total EXPR +@item -T EXPR +sets the value expression used for the ``totals'' column in the +@command{register} and @command{balance} reports. +@end table +@c @node Search Terms, Output Customization, Report Filtering, Detailed Options Description +@c @subsection Search Terms + +@c Valid Ledger invocations look like: +@c @smallexample +@c ledger [OPTIONS] <COMMAND> <SEARCH-TERMS> +@c @end smallexample + +@c Where @code{COMMAND} is any command verb (@pxref{Reporting Commands}), @code{OPTIONS} can occur +@c anywhere, and @code{SEARCH-TERM} is one or more of the following: + +@c @smallexample +@c word search for any account containing 'word' +@c TERM and TERM boolean AND between terms +@c TERM or TERM boolean OR between terms +@c not TERM invert the meaning of the term +@c payee word search for any payee containing 'word' +@c @@word shorthand for 'payee word' +@c desc word alternate for 'payee word' +@c note word search for any note containing 'word' +@c &word shorthand for 'note word' +@c tag word search for any metadata tag containing 'word' +@c tag word=value search for any metadata tag containing 'word' +@c whose value contains 'value' +@c %word shorthand for 'tag word' +@c %word=value shorthand for 'tag word=value' +@c meta word alternate for 'tag word' +@c meta word=value alternate for 'tag word=value' +@c expr 'EXPR' apply the given value expression as a predicate +@c '=EXPR' shorthand for 'expr EXPR' +@c \( TERMS \) group terms; useful if using and/or/not +@c @end smallexample + +@c So, to list all transaction that charged to ``food'' but not ``dining'' +@c for any payee other than ``chang'' the following three commands would be +@c equivalent: + +@c @smallexample +@c ledger reg food not dining @@chang +@c ledger reg food and not dining and not payee chang +@c ledger reg food not dining expr 'payee =~ /chang/' +@c @end smallexample + +@node Output Customization, Commodity Reporting, Report Filtering, Detailed Options Description @subsection Output Customization These options affect only the output, but not which postings are used to create it: -@option{--collapse} (@option{-n}) causes transactions in a -@command{register} report with multiple postings to be collapsed +@table @code +@item --collapse +@item -n +causes transactions in a @command{register} report with multiple +postings to be collapsed into a single, subtotaled transaction. + +@item --subtotal +@item -s +causes all transactions in a @command{register} report to be collapsed into a single, subtotaled transaction. -@option{--subtotal} (@option{-s}) causes all transactions in a -@command{register} report to be collapsed into a single, subtotaled -transaction. - -@option{--by-payee} (@option{-P}) reports subtotals by payee. - - -@option{--empty} (@option{-E}) includes even empty accounts in the -@command{balance} report. - -@option{--weekly} (@option{-W}) reports posting totals by the -week. The week begins on whichever day of the week begins the month -containing that posting. To set a specific begin date, use a -period string, such as @samp{weekly from DATE}. @option{--monthly} -(@option{-M}) reports posting totals by month; @option{--yearly} -(@option{-Y}) reports posting totals by year. For more complex -period, using the @option{--period} option described above. - -@option{--dow} reports postings 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{--pivot TAG} produces a pivot table around the tag provided. -This requires meta data using valued tags. - -@option{--wide} (@option{-w}) causes the default @command{register} -report to assume 132 columns instead of 80. - -@option{--head} causes only the first N transactions to be printed. This -is different from using the command-line utility @command{head}, which -would limit to the first N postings. @option{--tail} outputs only -the last N transactions. 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 transactions 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 posting -value. - -@option{--deviation} (@option{-D}) reports each posting's -deviation from the average. It is only meaningful in the -@command{register} and @command{prices} reports. - -@option{--percent} (@option{-%}) shows account subtotals in the -@command{balance} report as percentages of the parent account. - -@c @option{--totals} include running total information in the +@item --by-payee +@item -P +reports subtotals by payee. + + +@item --empty +@item -E +includes even empty accounts in the @command{balance} report. + +@item --weekly +@item -W +reports posting totals by the week. The week begins on whichever day of +the week begins the month containing that posting. To set a specific +begin date, use a period string, such as @code{weekly from DATE}. +@item --monthly +@item -M +reports posting totals by month; +@item --yearly +@item -Y +reports posting totals by year. For more complex period, using the +@item --period +option described above. + +@item --dow +reports postings totals for each day of the week. This is an easy way +to see if weekend spending is more than on weekdays. + +@item --sort EXPR +@item -S EXPR +sorts a report by comparing the values determined using the value +expression @var{EXPR}. For example, using @code{-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}. + +@item --pivot TAG +produces a pivot table around the tag provided. This requires meta data +using valued tags. + +@item --wide +@item -w +causes the default @command{register} report to assume 132 columns +instead of 80. + +@item --head +causes only the first @code{N} transactions to be printed. This is different +from using the command-line utility @command{head}, which would limit to +the first N postings. @code{--tail} outputs only the last @code{N} +transactions. 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 transactions being printed, for example, it would print all +but the first five). + +@item --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. + +@item --average +@item -A +reports the average posting value. + +@item --deviation +@item -D +reports each posting's deviation from the average. It is only +meaningful in the @command{register} and @command{prices} reports. + +@item --percent +@item -% +shows account subtotals in the @command{balance} report as percentages +of the parent account. + +@c @code{--totals} include running total information in the @c @command{xml} report. -@option{--amount-data} (@option{-j}) changes the @command{register} -report so that it outputs nothing but the date and the value column, -and the latter without commodities. This is only meaningful if the -report uses a single commodity. This data can then be fed to other -programs, which could plot the date, analyze it, etc. +@item --amount-data +@item -j +changes the @command{register} report so that it outputs nothing but the +date and the value column, and the latter without commodities. This is +only meaningful if the report uses a single commodity. This data can +then be fed to other programs, which could plot the date, analyze it, +etc. -@option{--total-data} (@option{-J}) changes the @command{register} -report so that it outputs nothing but the date and totals column, -without commodities. +@item --total-data +@item -J +changes the @command{register} report so that it outputs nothing but the +date and totals column, without commodities. -@option{--display EXPR} (@option{-d EXPR}) limits which postings -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 postings, against a running balance which -includes all posting values: +@item --display EXPR +@item -d EXPR +limits which postings 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 postings, against a running +balance which includes all posting values: @smallexample ledger -d "d>=[last month]" reg checking @@ -5822,41 +5970,136 @@ ledger -p "last month" reg checking @end smallexample 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 +amount for the reporting range (@code{-p}), or simply a display +restricted to the reporting range (using @code{-d}). + +@item --date-format STR +@item -y STR +changes the basic date format used by reports. The default uses a date +like @code{2004/08/01}, which represents the default date format of +@code{%Y/%m/%d}. To change the way dates are printed in general, the +easiest way is to put @code{--date-format FORMAT} in the Ledger +initialization file @file{~/.ledgerrc} (or the file referred to by +@env{LEDGER_INIT}). + +@item --format STR +@item -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: + +@item --balance-format STR +Define the output format for the @code{balance} report. The default (defined in @code{report.h} is: +@smallexample + "%(ansify_if( + justify(scrub(display_total), 20, + 20 + int(prepend_width), true, color), + bold if should_bold)) + %(!options.flat ? depth_spacer : \"\") + %-(ansify_if( + ansify_if(partial_account(options.flat), blue if color), + bold if should_bold))\n%/ + %$1\n%/ + %(prepend_width ? \" \" * int(prepend_width) : \"\") + --------------------\n" +@end smallexample +@item --cleared-format +Defines the format for the cleared report. The default is: +@smallexample + "%(justify(scrub(get_at(display_total, 0)), 16, 16 + int(prepend_width), + true, color)) %(justify(scrub(get_at(display_total, 1)), 18, + 36 + int(prepend_width), true, color)) + %(latest_cleared ? format_date(latest_cleared) : \" \") + %(!options.flat ? depth_spacer : \"\") + %-(ansify_if(partial_account(options.flat), blue if color))\n%/ + %$1 %$2 %$3\n%/ + %(prepend_width ? \" \" * int(prepend_width) : \"\") + ---------------- ---------------- ---------\n" +@end smallexample +@item --register-format STR +Define the output format for the @code{register} report. The default (defined in @code{report.h} is: +@smallexample + "%(ansify_if( + ansify_if(justify(format_date(date), int(date_width)), + green if color and date > today), + bold if should_bold)) + %(ansify_if( + ansify_if(justify(truncated(payee, int(payee_width)), int(payee_width)), + bold if color and !cleared and actual), + bold if should_bold)) + %(ansify_if( + ansify_if(justify(truncated(display_account, int(account_width), + int(abbrev_len)), int(account_width)), + blue if color), + bold if should_bold)) + %(ansify_if( + justify(scrub(display_amount), int(amount_width), + 3 + int(meta_width) + int(date_width) + int(payee_width) + + int(account_width) + int(amount_width) + int(prepend_width), + true, color), + bold if should_bold)) + %(ansify_if( + justify(scrub(display_total), int(total_width), + 4 + int(meta_width) + int(date_width) + int(payee_width) + + int(account_width) + int(amount_width) + int(total_width) + + int(prepend_width), true, color), + bold if should_bold))\n%/ + %(justify(\" \", int(date_width))) + %(ansify_if( + justify(truncated(has_tag(\"Payee\") ? payee : \" \", + int(payee_width)), int(payee_width)), + bold if should_bold)) + %$3 %$4 %$5\n" +@end smallexample +@item --csv-format +Sets the format for @code{csv} reports. The default is: +@smallexample +"%(quoted(date)), + %(quoted(code)), + %(quoted(payee)), + %(quoted(display_account)), + %(quoted(commodity)), + %(quoted(quantity(scrub(display_amount)))), + %(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))), + %(quoted(join(note | xact.note)))\n" +@end smallexample +@item --plot-amount-format STR +Sets the format for amount plots, using the @code{-j} option. The default is: +@smallexample +"%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n" +@end smallexample +@item --plot-total-format STR +Sets the format for total plots, using the @code{-J} option. The default is: +@smallexample +"%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n" +@end smallexample +@item --pricedb-format STR +Sets the format expected for the historical price file. The default is +@smallexample +"P %(datetime) %(display_account) %(scrub(display_amount))\n" +@end smallexample + +@item --prices-format STR +Sets the format for the @command{prices} report. The default is: +@smallexample +"%(date) %-8(display_account) %(justify(scrub(display_amount), 12, + 2 + 9 + 8 + 12, true, color))\n" +@end smallexample +@item --wide-register-format STR +(-w @command{register}) +@end table @node Commodity Reporting, Environment Variables, Output Customization, Detailed Options Description @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 start up, to -determine historical prices. Other settings can be placed in this -file manually, to prevent downloading quotes for a specific commodity, for -example. This is done by adding a line like the following: +@table @code +@item --price-db FILE +sets the file that is used for recording downloaded commodity prices. +It is always read on start up, to determine historical prices. Other +settings can be placed in this file manually, to prevent downloading +quotes for a specific commodity, for example. This is done by adding a +line like the following: @smallexample ; Don't download quotes for the dollar, or timelog values @@ -5864,27 +6107,34 @@ N $ N h @end smallexample -Note: Ledger NEVER write output to files. You are responsible for -updated the price-db file. The best way is to have your price download +@noindent Note: Ledger NEVER writes output to files. You are responsible for +updating the price-db file. The best way is to have your price download script maintain this file. -@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}. - +The format of the file can be changed by telling ledger to use the +@code{--pricedb-format} you define. + +@item --price-exp MINS +@item -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 +@code{--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. + +@item --download +@item -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}. +@end table 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, +expressions, and the @code{-t} and @code{-T} options. However, there are also several ``default'' reports, which will satisfy most users basic reporting needs: @@ -5912,10 +6162,10 @@ commodity can mean different things to different people, depending on the accounts involved, the commodities, the nature of the transactions, etc. -When you specify @samp{-V}, or @samp{-X COMM}, you are requesting that +When you specify @code{-V}, or @code{-X COMM}, you are requesting that some or all of the commodities be valuated as of today (or whatever -@samp{--now} is set to). But what does such a valuation mean? This -meaning is governed by the presence of a @samp{VALUE} meta-data property, +@code{--now} is set to). But what does such a valuation mean? This +meaning is governed by the presence of a @code{VALUE} meta-data property, whose content is an expression used to compute that value. If no VALUE property is specified, each posting is assumed to have a @@ -5926,9 +6176,9 @@ follows: = expr true ; VALUE:: market(amount, date, exchange) @end smallexample -This definition emulates the present day behavior of @samp{-V} and @samp{-X} (in the -case of @samp{-X}, the requested commodity is passed via the string 'exchange' -above). +This definition emulates the present day behavior of @code{-V} and +@code{-X} (in the case of @code{-X}, the requested commodity is passed +via the string 'exchange' above). @cindex Euro conversion One thing many people have wanted to do is to fixate the valuation of @@ -5940,8 +6190,8 @@ old European currencies in terms of the Euro after a certain date: ; VALUE:: date < [Jun 2008] ? market(amount, date, exchange) : 1.44 EUR @end smallexample -This says: If @samp{--now} is some old date, use market prices as they -were at that time; but if @samp{--now} is past June 2008, use a fixed +This says: If @code{--now} is some old date, use market prices as they +were at that time; but if @code{--now} is past June 2008, use a fixed price for converting Deutsch Mark to Euro. Or how about never re-valuating commodities used in Expenses, since they @@ -5954,7 +6204,7 @@ cannot have a different future value: This says the future valuation is the same as the valuation at the time of posting. post.date equals the posting's date, while just 'date' is -the value of @samp{--now} (defaults to today). +the value of @code{--now} (defaults to today). Or how about valuating miles based on a reimbursement rate during a specific time period: @@ -5966,7 +6216,7 @@ specific time period: @end smallexample In this case, miles driven in 2007 will always be valuated at $1.05 -each. If you use @samp{-X EUR} to expressly request all amounts in +each. If you use @code{-X EUR} to expressly request all amounts in Euro, Ledger shall convert $1.05 to Euro by whatever means are appropriate for dollars. @@ -6006,16 +6256,16 @@ another currency. For example: Ledger presently has no way of handling such things as FIFO and LIFO. If you specify an unadorned commodity name, like AAPL, it will balance -against itself. If @samp{--lots} are not being displayed, then it will +against itself. If @code{--lots} are not being displayed, then it will appear to balance against any lot of AAPL. @cindex adorned commodity If you specify an adorned commodity, like AAPL @{$10.00@}, it will also -balance against itself, and against any AAPL if @samp{--lots} is not -specified. But if you do specify @samp{--lot-prices}, for example, then +balance against itself, and against any AAPL if @code{--lots} is not +specified. But if you do specify @code{--lot-prices}, for example, then it will balance against that specific price for AAPL. -Normally when you use @samp{-X <commodity>} to request that amounts be reported in a +Normally when you use @code{-X <commodity>} to request that amounts be reported in a specific commodity, Ledger uses these values: @itemize @@ -6028,16 +6278,17 @@ specific commodity, Ledger uses these values: For the balance report, use the value of that commodity as of today. @end itemize -You can now specify -H to ask that all valuations for any amount be done +You can now specify @code{-H} to ask that all valuations for any amount be done relative to the date that amount was encountered. -You can also now use -X (and -H) in conjunction with -B and -I, to see -valuation reports of just your basis costs or lot prices. +You can also now use @code{-X} (and @code{-H}) in conjunction with +@code{-B} and @code{-I}, to see valuation reports of just your basis +costs or lot prices. @node Environment Variables, , Commodity Reporting, Detailed Options Description @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 +an option has a long name such @code{--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 @@ -6114,7 +6365,7 @@ last week 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 +case, the period @code{oct}, for example, will cover all the days in October. The possible forms are: @smallexample @@ -6139,7 +6390,7 @@ weekly last august @end smallexample -@node Budgeting and Forecasting, Value Expressions, Command-line Syntax, Top +@node Budgeting and Forecasting, Time Keeping, Command-line Syntax, Top @chapter Budgeting and Forecasting @menu @@ -6188,7 +6439,7 @@ ledger -p "this year" --monthly --average --subtotal balance ^expenses The reported totals are the current year's average for each account. Once these period transactions are defined, creating a budget report is as -easy as adding @option{--budget} to the command-line. For example, a +easy as adding @code{--budget} to the command-line. For example, a typical monthly expense report would be: @example @@ -6203,8 +6454,8 @@ ledger --budget --monthly register ^expenses 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 un-budgeted expenses -using @option{--unbudgeted}: +@code{--add-budget}. You can even see only the un-budgeted expenses +using @code{--unbudgeted}: @example ledger --unbudgeted --monthly register ^expenses @@ -6235,19 +6486,48 @@ only, and not against the running total: ledger --forecast "d<[2010]" bal ^assets ^liabilities @end example +@node Time Keeping, Value Expressions, Budgeting and Forecasting, Top +@chapter Time Keeping + -@node Value Expressions, Format Strings, Budgeting and Forecasting, Top +Ledger directly supports ``timelog'' entries, which have this form: + +@smallexample + i 2013/03/28 22:13:00 ACCOUNT[ PAYEE] + o 2013/03/29 03:39:00 +@end smallexample + +This records a check-in to the given ACCOUNT, and a check-out. You can +be checked-in to multiple accounts at a time, if you wish, and they can +span multiple days (use @code{--day-break} to break them up in the +report). The number of seconds between is accumulated as time to that +ACCOUNT. If the checkout uses a capital ``O'', the transaction is marked +``cleared''. You can use an optional PAYEE for whatever meaning you like. + +Now, there are a few ways to generate this information. You can use the +@file{timeclock.el} package, which is part of Emacs. Or you can write a +simple script in whichever language you prefer to emit similar +information. Or you can use Org mode's time-clocking abilities and the +org2tc script developed by John Wiegly. + +These timelog entries can appear in a separate file, or directly in your +main ledger file. The initial "i" and "o" count as Ledger "directives", +and are accepted anywhere that ordinary transactions are. + + + +@node Value Expressions, Format Strings, Time Keeping, Top @chapter Value Expressions -Value expressions are an expression language used by Ledger to -calculate values used by the program for many different purposes: +Ledger uses value expressions to make calculations for many different +purposes: @enumerate @item The values displayed in reports @item For predicates (where truth is anything non-zero), to determine which -postings are calculated (@option{-l}) or displayed (@option{-d}). +postings are calculated (@code{-l}) or displayed (@code{-d}). @item For sorting criteria, to yield the sort key. @item @@ -6255,19 +6535,21 @@ In the matching criteria used by automated postings. @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: +addition to a set of functions and variables. -@smallexample -ledger -d /^Liabilities/?T<0:UT>100 balance -@end smallexample +@c A function's +@c argument is whatever follows it. The following is a display predicate +@c that I use with the @command{balance} command: + +@c @smallexample +@c ledger -d /^Liabilities/?T<0:UT>100 balance +@c @end smallexample -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. +@c The effect is that account totals are displayed only if: 1) A +@c Liabilities account has a total less than zero; or 2) the absolute +@c value of the account's total exceeds 100 units of whatever commodity +@c contains. If it contains multiple commodities, only one of them must +@c exceed 100 units. Display predicates are also very handy with register reports, to constrain which transactions are printed. For example, the following @@ -6279,8 +6561,8 @@ ledger -d "d>[this month]" register checking @end smallexample This advantage to this command's complexity is that it prints the -running total in terms of all transactions in the register. The following, -simpler command is similar, but totals only the displayed +running total in terms of all transactions in the register. The +following, simpler command is similar, but totals only the displayed postings: @smallexample @@ -6301,20 +6583,20 @@ Below are the one letter variables available in any value expression. For the register and print commands, these variables relate to individual postings, and sometimes the account affected by a posting. For the balance command, these variables relate to -accounts---often with a subtle difference in meaning. The use of each +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 +This maps to whatever the user specified with @code{-t}. In a +register report, @code{-t} changes the value column; in a balance +report, it has no meaning by default. If @code{-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 +This maps to whatever the user specified with @code{-T}. In a +register report, @code{-T} changes the totals column; in a balance +report, this is the value given for each account. If @code{-T} was not specified, the current report style's value expression is used. @item m @@ -6341,7 +6623,7 @@ The market value of a posting, or an account without its children. @item g The net gain (market value minus cost basis), for a posting or an -account without its children. It is the same as @samp{v-b}. +account without its children. It is the same as @code{v-b}. @item l The depth (``level'') of an account. If an account has one parent, @@ -6383,7 +6665,7 @@ all its children. @item G The total net gain (market value minus cost basis), for a series of postings, or an account and its children. It is the same as -@samp{V-B}. +@code{V-B}. @end table @node Functions, Operators, Variables, Value Expressions @@ -6402,12 +6684,12 @@ The absolute (unsigned) value of the argument. Strips the commodity from the argument. @item A -The arithmetic mean of the argument; @samp{Ax} is the same as -@samp{x/n}. +The arithmetic mean of the argument; @code{Ax} is the same as +@code{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 +The present market value of the argument. The syntax @code{P(x,d)} is +supported, which yields the market value at time @code{d}. If no date is given, then the current moment is used. @end table @@ -6417,10 +6699,10 @@ is given, then the current moment is used. The binary and ternary operators, in order of precedence, are: @enumerate -@item @samp{* /} -@item @samp{+ -} -@item @samp{! < > =} -@item @samp{& | ?:} +@item @code{* /} +@item @code{+ -} +@item @code{! < > =} +@item @code{& | ?:} @end enumerate @menu @@ -6502,177 +6784,237 @@ precedence order of operators. @item [DATE] Useful specifying a date in plain terms. For example, you could say -@samp{[2004/06/01]}. +@code{[2004/06/01]}. @end table +@menu +* Misc:: +@end menu + +@node Misc, , Complex Expressions, Complex Expressions +@subsection Miscellaneous +@multitable @columnfractions .3 .2 .5 +@item @strong{Function} @tab @strong{Abbrev.} @tab @strong{Description} +@item @code{amount_expr } @tab @code{} @tab +@item @code{abs } @tab @code{} @tab --> U +@item @code{ceiling } @tab @code{} @tab Returns the next integer toward +infty +@item @code{code} @tab @code{} @tab returns the transaction code, the string between the parenthesis after the date. +@item @code{commodity } @tab @code{} @tab +@item @code{display_amount } @tab @code{} @tab --> t +@item @code{display_total } @tab @code{} @tab --> T +@item @code{date } @tab @code{} @tab +@item @code{format_date } @tab @code{} @tab +@item @code{format } @tab @code{} @tab +@item @code{floor } @tab @code{} @tab Returns the next integer toward -infty +@item @code{get_at } @tab @code{} @tab +@item @code{is_seq } @tab @code{} @tab +@item @code{justify } @tab @code{} @tab +@item @code{join } @tab @code{} @tab +@item @code{market --> P } @tab @code{} @tab +@item @code{null } @tab @code{} @tab +@item @code{now --> d m } @tab @code{} @tab +@item @code{options } @tab @code{} @tab +@item @code{post } @tab @code{} @tab +@item @code{percent } @tab @code{} @tab +@item @code{price } @tab @code{} @tab +@item @code{print } @tab @code{} @tab +@item @code{quoted } @tab @code{} @tab +@item @code{quantity } @tab @code{} @tab +@item @code{rounded } @tab @code{} @tab +@item @code{roundto } @tab @code{} @tab Returns value rounded to n digits. Does not affect formatting. +@item @code{scrub } @tab @code{} @tab +@item @code{strip --> S } @tab @code{} @tab +@item @code{should_bold } @tab @code{} @tab +@item @code{truncated } @tab @code{} @tab +@item @code{total_expr } @tab @code{} @tab +@item @code{today } @tab @code{} @tab +@item @code{top_amount } @tab @code{} @tab +@item @code{to_boolean } @tab @code{} @tab +@item @code{to_int } @tab @code{} @tab +@item @code{to_datetime } @tab @code{} @tab +@item @code{to_date } @tab @code{} @tab +@item @code{to_amount } @tab @code{} @tab +@item @code{to_balance } @tab @code{} @tab +@item @code{to_spring } @tab @code{} @tab +@item @code{to_mask } @tab @code{} @tab +@item @code{to_sequence } @tab @code{} @tab +@item @code{unrounded } @tab @code{} @tab +@item @code{value_date } @tab @code{} @tab +@end multitable -@node Format Strings, Ledger for Developers, Value Expressions, Top +@node Format Strings, Extending with Python, Value Expressions, Top @chapter Format Strings @menu * Basics:: +* Format String Structure:: * Format Expressions:: * --balance-format:: -* New formatting codes:: -* Date and Time Format Codes:: +* Formatting codes:: @end menu -@node Basics, Format Expressions, Format Strings, Format Strings +@node Basics, Format String Structure, Format Strings, Format Strings @section Format String Basics -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 posting in custom ways. +Format strings may be used to change the output format of reports. They +are specified by passing a formatting string to the @code{--format} +(@code{-F}) option. Within that string, constructs are allowed which +make it possible to display the various parts of an account or posting +in custom ways. + +There are several additional flags that allow you to define formats for +specific reports. These are useful to define in your configuration file +and will allow you to run ledger reports from the command line without +having to enter a new format for each command. + +@itemize +@item @code{--balance-report} +@item @code{--cleared-report} +@item @code{--register-report} +@item @code{--csv-report} +@item @code{--plot-amount-report} +@item @code{--plot-total-report} +@item @code{--pricedb-report} +@item @code{--prices-report} +@item @code{--wide-register-report} +@end itemize +@node Format String Structure, Format Expressions, Basics, Format Strings +@section Format String Structure Within a format string, a substitution is specified using a percent -character (@samp{%}). The basic format of all substitutions is: +character (@code{%}). The basic format of all substitutions is: @smallexample %[-][MIN WIDTH][.MAX WIDTH](VALEXPR) @end smallexample -If the optional minus sign (@samp{-}) follows the percent character, +If the optional minus sign (@code{-}) 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: +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: -@smallexample -%-P a transaction'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 smallexample +@table @code +@item %-20P +a transaction's payee, left justified and padded to 20 characters wide. +@item %20P +The same, right justified, at least 20 chars wide +@item %.20P +The same, no more than 20 chars wide +@end table -The expression following the format constraints can be a single -letter, or an expression enclosed in parentheses or brackets. +The expression following the format constraints can be a single letter, +or an expression enclosed in parentheses or brackets. -@node Format Expressions, --balance-format, Basics, Format Strings +@node Format Expressions, --balance-format, Format String Structure, Format Strings @section Format Expressions - The -allowable expressions are: + 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 +Inserts the results of the value expression specified by @code{-t}. +If @code{-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 +Inserts the results of the value expression specified by @code{-T}. +If @code{-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 +example, one could say @code{%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 posting's date with a date format string, exactly like those supported by @code{strftime}. For -example: @samp{%[%Y/%m/%d %H:%M:%S]}. +example: @code{%[%Y/%m/%d %H:%M:%S]}. @item S -Insert the pathname of the file from which the transaction's data was read. +Insert the pathname of the file from which the transaction's data was +read. Only sensible in a register report. @item B -Inserts the beginning character position of that transaction within the file. +Inserts the beginning character position of that transaction within the +file. @item b Inserts the beginning line of that transaction within the file. @item E -Inserts the ending character position of that transaction within the file. +Inserts the ending character position of that transaction within the +file. @item e Inserts the ending line of that transaction 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. +@c @item D +@c By default, this is the same as @code{%[%Y/%m%/d]}. The date format +@c used can be changed at any time with the @code{-y} flag, however. Using +@c @code{%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 transaction has an -effective date, in which case it prints -@samp{[ACTUAL_DATE=EFFECTIVE_DATE]}. +Returns the date according to the default format. If the transaction +has an effective date, it prints @code{[ACTUAL_DATE=EFFECTIVE_DATE]}. @item X -If a posting has been cleared, this inserts @samp{*} followed by a -space; otherwise nothing is inserted. +If a posting has been cleared, this returns a 1, otherwise returns 0. @item Y -This is the same as @samp{%X}, except that it only displays a state +This is the same as @code{%X}, except that it only displays a state character if all of the member postings have the same state. @item C -Inserts the checking number for a transaction, in parentheses, followed by -a space; if none was specified, nothing is inserted. +Inserts the transaction type. This is the value specified between +parenthesis on the first line of the transaction. @item P Inserts the payee related to a posting. -@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. +@c @item a +@c Inserts the optimal short name for an account. This is normally used in +@c balance reports. It prints a parent account's name if that name has not +@c 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 -posting's state @emph{if the transaction's posting 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. +@c @item W +@c This is the same as @code{%A}, except that it first displays the +@c posting's state @emph{if the transaction's posting states are not all +@c the same}, followed by the full account name. This is offered as a +@c printing optimization, so that combined with @code{%Y}, only the minimum +@c amount of state detail is printed. -@item o -Inserts the ``optimized'' form of a posting's amount. This is -used by the print report. In some cases, this inserts nothing; in -others, it inserts the posting amount and its cost. It's use is -not recommend unless you are modifying the print report. +@c @item o +@c Inserts the ``optimized'' form of a posting's amount. This is used by +@c the print report. In some cases, this inserts nothing; in others, it +@c inserts the posting amount and its cost. It's use is not recommended +@c unless you are modifying the print report. -@item n -Inserts the note associated with a posting, 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 posting, if one exists. @item / -The @samp{%/} construct is special. It separates a format string +The @code{%/} construct is special. It separates a format string between what is printed for the first posting of a transaction, and what is printed for all subsequent postings. If not used, the same format string is used for all postings. @end table -@node --balance-format, New formatting codes, Format Expressions, Format Strings +@node --balance-format, Formatting codes, Format Expressions, Format Strings @section --balance-format -As an example of how flexible the --format strings can be, the default balance format looks like this: +As an example of how flexible the @code{--format} strings can be, the default +balance format looks like this (the various functions are described later): @smallexample "%(justify(scrub(display_total), 20, -1, true, color))" @@ -6682,33 +7024,37 @@ As an example of how flexible the --format strings can be, the default balance f "--------------------\n" @end smallexample -@node New formatting codes, Date and Time Format Codes, --balance-format, Format Strings -@section New Formatting Codes +@node Formatting codes, , --balance-format, Format Strings +@section Formatting Functions and Codes @menu * Field Widths:: * Colors:: * Quantities and Calculations:: * Dates:: +* Date and Time Format Codes:: * Text Formatting:: -* Misc:: +* Data File Parsing Information:: @end menu -@node Field Widths, Colors, New formatting codes, New formatting codes +@node Field Widths, Colors, Formatting codes, Formatting codes @subsection Field Widths -@multitable @columnfractions .3 .2 .5 -@item @strong{Function} @tab @strong{Abbrev.} @tab @strong{Description} +The following codes return the width allocated for the specific fields. +The defaults can be changed using the corresponding command line +options: +@itemize @item @code{date_width} @item @code{payee_width} @item @code{account_width} @item @code{amount_width} @item @code{total_width} -@end multitable +@end itemize -@node Colors, Quantities and Calculations, Field Widths, New formatting codes +@node Colors, Quantities and Calculations, Field Widths, Formatting codes @subsection Colors -The character based formatting ledger can do is limited to the ANSI terminal character colors and font highlight in a normal TTY session. +The character based formatting ledger can do is limited to the ANSI +terminal character colors and font highlights in a normal TTY session. @multitable @columnfractions .3 .3 .3 @item @code{red} @tab @code{magenta} @tab @code{bold} @item @code{green } @tab @code{cyan} @tab @code{underline} @@ -6718,64 +7064,166 @@ The character based formatting ledger can do is limited to the ANSI terminal cha -@node Quantities and Calculations, Dates, Colors, New formatting codes +@node Quantities and Calculations, Dates, Colors, Formatting codes @subsection Quantities and Calculations -@multitable @columnfractions .3 .2 .5 -@item @strong{Function} @tab @strong{Abbrev.} @tab @strong{Description} -@item @code{amount_expr } @tab @code{} @tab -@item @code{abs} @tab @code{U} @tab -@item @code{commodity } @tab @code{} @tab -@item @code{display_amount } @tab @code{t} @tab -@item @code{display_total } @tab @code{T} @tab -@item @code{floor } @tab @code{} @tab -@item @code{get_at } @tab @code{} @tab -@item @code{is_seq } @tab @code{} @tab -@item @code{market } @tab @code{P} @tab -@item @code{percent } @tab @code{} @tab -@item @code{price } @tab @code{} @tab -@item @code{quantity } @tab @code{} @tab -@item @code{rounded } @tab @code{} @tab -@item @code{truncated } @tab @code{} @tab -@item @code{total_expr } @tab @code{} @tab -@item @code{top_amount } @tab @code{} @tab -@item @code{to_boolean } @tab @code{} @tab -@item @code{to_int } @tab @code{} @tab -@item @code{to_amount } @tab @code{} @tab -@item @code{to_balance } @tab @code{} @tab -@item @code{unrounded } @tab @code{} @tab -@end multitable +@table @code +@item amount_expr +@item abs +@item commodity +@item display_amount +@item display_total +@item floor +@item get_at +@item is_seq +@item market +@item percent +@item price +@item quantity +@item rounded +@item truncated +@item total_expr +@item top_amount +@item to_boolean +@item to_int +@item to_amount +@item to_balance +@item unrounded +@end table -@node Dates, Text Formatting, Quantities and Calculations, New formatting codes -@subsection Dates +@node Dates, Date and Time Format Codes, Quantities and Calculations, Formatting codes +@subsection Date Functions +The following functions allow you to manipulate and format dates. +@table @code +@item date +Returns the date of the current transaction +@item format_date(date, "FORMAT STRING") +formats the date using the given format string. +@item now +Returns the current date and time. If the @code{--now} option is +defined it will return that value. +@item today +Returns the current date. If the @code{--now} option is +defined it will return that value. +@item to_datetime +convert a string to a date-time value +@item to_date +convert a string to date value +@item value_date +@end table -@multitable @columnfractions .3 .2 .5 -@item @strong{Function} @tab @strong{Abbrev.} @tab @strong{Description} -@item @code{date } @tab @code{} @tab -@item @code{format_date } @tab @code{} @tab -@item @code{now } @tab @code{} @tab --> d m -@item @code{today } @tab @code{} @tab -@item @code{to_datetime } @tab @code{} @tab -@item @code{to_date } @tab @code{} @tab -@item @code{value_date } @tab @code{} @tab -@end multitable +@menu +* Date and Time Format Codes:: +@end menu + +@node Date and Time Format Codes, Text Formatting, Dates, Formatting codes +@subsection Date and Time Format Codes +Date and time format are specified as strings of single letter codes +preceded by percent signs. Any separator, or no separator can be +specified. +@subsubsection Days +Dates are formed from a combination of day, month and year codes, in +whatever order you prefer: + +@table @code +@item %Y +Four digit year + +@item %y +Two digit year + +@item %m +Two digit month + +@item %d +Two digit date +@end table + +@noindent So @code{"%Y%m%d"} yields @code{20111214} which provides a date that is simple to sort on. + +@subsubsection Weekdays + +You can have additional weekday information in your date with @code{%A} +as + +@table @code +@item %m-%d-%Y %A +yields @code{02-10-2010 Wednesday} + +@item %A %m-%d-%Y +yields @code{Wednesday 02-10-2010} +@end table +@noindent These are options you can select for weekday + +@table @code +@item %a +weekday, abbreviated Wed +@item %A +weekday, full Wednesday +@item %d +day of the month (dd), zero padded 10 +@item %e +day of the month (dd) 10 +@item %j +day of year, zero padded 000-366 +@item %u +day of week starting with Monday (1), i.e. @code{mtwtfss} 3 +@item %w +day of week starting with Sunday (0), i.e. @code{smtwtfs} 3 +@end table +@subsubsection Month + +You can have additional month information in your date with @code{%B} as + +@table @code +@item %m-%d-%Y %B +yields @code{ 02-10-2010 Februrary} + +@item %B %m-%d-%Y +yields @code{February 02-10-2010} +@end table +@noindent These are options you can select for month +@table @code +@item %m +@code{mm} month as two digits + +@item %b +Locale’s abbreviated month, for example @code{02} might be abbreviated as @code{Feb} -@node Text Formatting, Misc, Dates, New formatting codes +@item %B +Locale’s full month, variable length February +@end table + +@subsubsection Miscellaneous Date Codes +Additional date format parameters which can be used : + +@table @code +@item %U +week number Sunday as first day of week 01–53 +@item %W +week number Monday as first day of week 01–53 +@item %V +week of the year 01–53 +@item %C +@code{cc} century 00–99 +@item %D +yields @code{mm/dd/yy 02/10/10} +@item %x +locale’s date representation @code{02/10/2010} for the U.S. +@item %F +yields @code{%Y-%m-%d 2010-02-10} +@end table +@menu +* Text Formatting:: +* Data File Parsing Information:: +* Misc:: +@end menu + +@node Text Formatting, Data File Parsing Information, Date and Time Format Codes, Formatting codes @subsection Text Formatting -@subsubsection Summary -@multitable @columnfractions .6 .4 -@item @strong{Function} @tab @strong{Description} -@item @code{ansify_if(str,color) } @tab Colorize the string -@item @code{justify(str, fwidth, lwidth, right, colorize) } @tab Right or left justify the string. -@item @code{join(str) } @tab Remove line feeds from the input string. Mainly used internally for org-mode output -@item @code{quoted(str) } @tab Returns @code{"<str>"}. -@item @code{strip } @tab @code{Removes additional annotations from values.} -@item @code{scrub } @tab @code{S} -@item @code{should_bold } @tab @code{} -@end multitable -@subsubsection Detailed Descriptions +The following format functions allow you limited formatting of text: @table @code @item ansify_if(value, color) Surrounds the string representing value with ANSI codes to give it @@ -6789,149 +7237,203 @@ If @code{right_justify=true} then the field is right justify within the width of the field. If it is @code{false}, then the field is left justified and padded to the full width of the field. If @code{colorize} is true then ledger will honor color settings. -@item join(str) -Replaces line feeds in str with @code{\n}. -@item quoted(str) -Return str surrounded by double quotes, @code{"<str>"}. +@item join(STR) +Replaces line feeds in @code{STR} with @code{\n}. +@item quoted(STR) +Return @code{STR} surrounded by double quotes, @code{"STR"}. @item strip(value) Values can have numerous annotations, such as effective dates and lot prices. @code{strip} removes these annotations. @end table -@node Misc, , Text Formatting, New formatting codes -@subsection Miscellaneous -@multitable @columnfractions .3 .2 .5 -@item @strong{Function} @tab @strong{Abbrev.} @tab @strong{Description} -@item @code{amount_expr } @tab @code{} @tab -@item @code{abs } @tab @code{} @tab --> U -@item @code{code} @tab @code{} @tab returns the transaction code, the string between the parenthesis after the date. -@item @code{commodity } @tab @code{} @tab -@item @code{display_amount } @tab @code{} @tab --> t -@item @code{display_total } @tab @code{} @tab --> T -@item @code{date } @tab @code{} @tab -@item @code{format_date } @tab @code{} @tab -@item @code{format } @tab @code{} @tab -@item @code{floor } @tab @code{} @tab -@item @code{get_at } @tab @code{} @tab -@item @code{is_seq } @tab @code{} @tab -@item @code{justify } @tab @code{} @tab -@item @code{join } @tab @code{} @tab -@item @code{market --> P } @tab @code{} @tab -@item @code{null } @tab @code{} @tab -@item @code{now --> d m } @tab @code{} @tab -@item @code{options } @tab @code{} @tab -@item @code{post } @tab @code{} @tab -@item @code{percent } @tab @code{} @tab -@item @code{price } @tab @code{} @tab -@item @code{print } @tab @code{} @tab -@item @code{quoted } @tab @code{} @tab -@item @code{quantity } @tab @code{} @tab -@item @code{rounded } @tab @code{} @tab -@item @code{scrub } @tab @code{} @tab -@item @code{strip --> S } @tab @code{} @tab -@item @code{should_bold } @tab @code{} @tab -@item @code{truncated } @tab @code{} @tab -@item @code{total_expr } @tab @code{} @tab -@item @code{today } @tab @code{} @tab -@item @code{top_amount } @tab @code{} @tab -@item @code{to_boolean } @tab @code{} @tab -@item @code{to_int } @tab @code{} @tab -@item @code{to_datetime } @tab @code{} @tab -@item @code{to_date } @tab @code{} @tab -@item @code{to_amount } @tab @code{} @tab -@item @code{to_balance } @tab @code{} @tab -@item @code{to_spring } @tab @code{} @tab -@item @code{to_mask } @tab @code{} @tab -@item @code{to_sequence } @tab @code{} @tab -@item @code{unrounded } @tab @code{} @tab -@item @code{value_date } @tab @code{} @tab -@end multitable -@node Date and Time Format Codes, , New formatting codes, Format Strings -@section Date and Time Format Codes -Date and time format are specified as strings of single letter codes -preceded by percent signs. Any separator, or no separator can be -specified. -@subsection Dates -Dates are formed from a combination of day, month and year codes, in -whatever order you prefer: +@node Data File Parsing Information, , Text Formatting, Formatting codes +@subsection Data File Parsing Information -@option{%Y} is keyword for four digit year +The following format strings provide locational metadata +regarding the coordinates of entries in the source data file(s) that +generated the posting. -@option{%y} is keyword for two digit year +@table @code +@item filename +name of ledger data file from whence posting came, abbreviated @code{S} +@item beg_pos +character position in @code{filename} where entry for posting begins, abbreviated @code{B} +@item end_pos +character position in @code{filename} where entry for posting ends, abbreviated @code{E} +@item beg_line +line number in @code{filename} where entry for posting begins, abbreviated @code{b} +@item end_line +line number in @code{filename} where posting's entry for posting ends, abbreviated @code{e} +@end table -@option{%m} is keyword for two digit month -@option{%d} is keyword for two digit date -@noindent So @code{"%Y%m%d"} yields @code{20111214} which provides a date that is simple to sort. +@node Extending with Python, Ledger for Developers, Format Strings, Top +@chapter Extending with Python +Python can be used to extend your Ledger +experience. But first, a word must be said about Ledger's data model, so that +other things make sense later. + +@menu +* Basic data traversal:: +* Raw vs. Cooked:: +* Queries:: +* Embedded Python:: +* Amounts:: +@end menu -@subsection Weekdays +@node Basic data traversal, Raw vs. Cooked, Extending with Python, Extending with Python +@section Basic data traversal -You can have additional weekday information in your date with @code{%A} -as +Every interaction with Ledger happens in the context of a Session. Even if +you don't create a session manually, one is created for you by the top-level +interface functions. The Session is where objects live like the Commodity's +that Amount's refer to. -@option{%m-%d-%Y %A} yields @code{02-10-2010 Wednesday} +The make a Session useful, you must read a Journal into it, using the function +`@code{read_journal}`. This reads Ledger data from the given file, populates a +Journal object within the current Session, and returns a reference to that +Journal object. -@option{%A %m-%d-%Y} yields @code{Wednesday 02-10-2010} +Within the Journal live all the Transaction's, Posting's, and other objects +related to your data. There are also AutomatedTransaction's and +PeriodicTransaction's, etc. -@noindent These are options you can select for weekday +Here is how you would traverse all the postings in your data file: +@smallexample -@option{%a} weekday, abbreviated Wed + import ledger -@option{%A} weekday, full Wednesday + for xact in ledger.read_journal("sample.dat").xacts: + for post in xact.posts: + print "Transferring %s to/from %s" % (post.amount, post.account) +@end smallexample -@option{%d} day of the month (dd), zero padded 10 +@node Raw vs. Cooked, Queries, Basic data traversal, Extending with Python +@section Raw vs. Cooked -@option{%e} day of the month (dd) 10 +Ledger data exists in one of two forms: raw and cooked. Raw objects are what +you get from a traversal like the above, and represent exactly what was seen +in the data file. Consider this journal: -@option{%j} day of year, zero padded 000-366 +@smallexample + = true + (Assets:Cash) $100 -@option{%u} day of week starting with Monday (1), i.e. @code{mtwtfss} 3 + 2012-03-01 KFC + Expenses:Food $100 + Assets:Credit +@end smallexample -@option{%w} day of week starting with Sunday (0), i.e. @code{smtwtfs} 3 -@subsection Month +In this case, the @emph{raw} regular transaction in this file is: -You can have additional month information in your date with @code{%B} as +@smallexample + 2012-03-01 KFC + Expenses:Food $100 + Assets:Credit +@end smallexample +While the @emph{cooked} form is: -@option{%m-%d-%Y %B} yields @code{ 02-10-2010 Februrary} +@smallexample + 2012-03-01 KFC + Expenses:Food $100 + Assets:Credit $-100 + (Assets:Cash) $100 +@end smallexample -@option{%B %m-%d-%Y} yields @code{February 02-10-2010} +So the easy way to think about raw vs. cooked is that raw is the unprocessed +data, and cooked has had all considerations applied. -@noindent These are options you can select for month +When you traverse a Journal by iterating its transactions, you are generally +looking at raw data. In order to look at cooked data, you must generate a +report of some kind by querying the journal: -@option{%m} @code{mm} month as two digits +@smallexample + for post in ledger.read_journal("sample.dat").query("food"): + print "Transferring %s to/from %s" % (post.amount, post.account) +@end smallexample -@option{%b} @code{Mon}, locale’s abbreviated Feb +The reason why queries iterate over postings instead of transactions is that +queries often return only a ``slice'' of the transactions they apply to. You +can always get at a matching posting's transaction by looking at its "xact" +member: -@option{%B} locale’s full month, variable length February +@smallexample + last_xact = None + for post in ledger.read_journal("sample.dat").query(""): + if post.xact != last_xact: + for post in post.xact.posts: + print "Transferring %s to/from %s" % (post.amount, + post.account) + last_xact = post.xact +@end smallexample +This query ends up reporting every cooked posting in the Journal, but does it +transaction-wise. It relies on the fact that an unsorted report returns +postings in the exact order they were parsed from the journal file. -@subsection Miscellaneous Date Codes -Additional date format parameters which can be used : +@node Queries, Embedded Python, Raw vs. Cooked, Extending with Python +@section Queries + +The Journal.query() method accepts every argument you can specify on the +command-line, including --options. + +Since a query ``cooks'' the journal it applies to, only one query may be active +for that journal at a given time. Once the query object is gone (after the +for loop), then the data reverts back to its raw state. -@option{%U} week number Sunday as first day of week 01–53 +@node Embedded Python, Amounts, Queries, Extending with Python +@section Embedded Python -@option{%W} week number Monday as first day of week 01–53 +You can embed Python into your data files using the 'python' directive: -@option{%V} week of the year 01–53 +@smallexample + python + import os + def check_path(path_value): + print "%s => %s" % (str(path_value), os.path.isfile(str(path_value))) + return os.path.isfile(str(path_value)) -@option{%C} @code{cc} century 00–99 + tag PATH + assert check_path(value) -@option{%D} yields @code{mm/dd/yy 02/10/10} + 2012-02-29 KFC + ; PATH: somebogusfile.dat + Expenses:Food $20 + Assets:Cash +@end smallexample -@option{%x} locale’s date representation @code{02/10/2010} for the U.S. +Any Python functions you define this way become immediately available as +valexpr functions. -@option{%F} yields @code{%Y-%m-%d 2010-02-10} +@node Amounts, , Embedded Python, Extending with Python +@section Amounts + +When numbers come from Ledger, like post.amount, the type of the value is +Amount. It can be used just like an ordinary number, except that addition +and subtraction are restricted to amounts with the same commodity. If you +need to create sums of multiple commodities, use a Balance. For example: + +@smallexample + total = Balance() + for post in ledger.read_journal("sample.dat").query(""): + total += post.amount + print total +@end smallexample -@node Ledger for Developers, Extending with Python, Format Strings, Top + + +@node Ledger for Developers, Major Changes from version 2.6, Extending with Python, Top @chapter Ledger for Developers @menu * Internal Design:: * Journal File Format:: +* Developer Commands:: +* Ledger Development Environment:: @end menu @node Internal Design, Journal File Format, Ledger for Developers, Ledger for Developers @@ -6984,7 +7486,7 @@ Those tiers are: strings, etc. If you try to apply an operation between two values that makes no sense (like dividing an amount by a balance), an error occurs at runtime, rather than at compile-time (as would happen if you actually tried - to divide an amount_t by a balance_t). + to divide an @code{amount_t} by a @code{balance_t}). This is the core data type for the value expression language. @@ -7003,9 +7505,9 @@ Those tiers are: @item Query expressions Expressions can be onerous to type at the command-line, so there's a - shorthand for reporting called "query expressions". These add no + shorthand for reporting called ``query expressions''. These add no functionality of there own, but are purely translated from the input string - (cash) down to the corresponding value expression (account =~ /cash/). + (cash) down to the corresponding value expression @code{(account =~ /cash/)}. This is a convenience layer. @item Format strings @@ -7013,7 +7515,7 @@ Those tiers are: Format strings let you interpolate value expressions into string, with the requirement that any interpolated value have a string representation. Really all this does is calculate the value expression in the current - report context, call the resulting value's "to_string()" method, and stuffs + report context, call the resulting value's @code{to_string()} method, and stuffs the result into the output string. It also provides printf-like behavior, such as min/max width, right/left justification, etc. @@ -7046,19 +7548,19 @@ Those tiers are: @item The Journal object Finally, all transactions with their postings, and all accounts, are owned - by a journal_t object. This is the go-to object for querying ad reporting + by a @code{journal_t} object. This is the go-to object for querying ad reporting on your data. @item Textual journal parser There is a textual parser, wholly contained in textual.cc, which knows how - to parse text into journal objects, which then get "finalized" and added to + to parse text into journal objects, which then get ``finalized'' and added to the journal. Finalization is the step that enforces the double-entry guarantee. @item Iterators - Every journal object is "iterable", and these iterators are defined in + Every journal object is ``iterable'', and these iterators are defined in iterators.h and iterators.cc. This iteration logic is kept out of the basic journal objects themselves for the sake of modularity. @@ -7066,12 +7568,12 @@ Those tiers are: Another abstraction isolated to its own layer, this class encapsulating the comparison of journal objects, based on whatever value expression the user - passed to --sort. + passed to @code{--sort}. @item Temporaries Many reports bring pseudo-journal objects into existence, like postings - which report totals in a "<Total>" account. These objects are created and + which report totals in a @code{<Total>} account. These objects are created and managed by a temporaries_t object, which gets used in many places by the reporting filters. @@ -7103,7 +7605,7 @@ Those tiers are: iterator depends on the type of report. This iterator is then walked, and every object yielded from the iterator is - passed to an "item handler", whose type is directly related to the type of + passed to an ``item handler'', whose type is directly related to the type of the iterator. There are many, many item handlers, which can be chained together. Each @@ -7112,22 +7614,22 @@ Those tiers are: filters which compute the running totals; that queue and sort all the input items before playing them back out in a new order; that filter out items which fail to match a predicate, etc. Almost every reporting feature in - Ledger is related to one or more filters. Looking at filters.h, I see over + Ledger is related to one or more filters. Looking at @code{filters.h}, I see over 25 of them defined currently. @item The filter chain How filters get wired up, and in what order, is a complex process based on all the various options specified by the user. This is the job of the - chain logic, found entirely in chain.cc. It took a really long time to get + chain logic, found entirely in @code{chain.cc}. It took a really long time to get this logic exactly write, which is why I haven't exposed this layer to the Python bridge yet. @item Output modules Although filters are great and all, in the end you want to see stuff. This - is the job of special "leaf" filters call output modules. They are - implemented just like a regular filter, but they don't have a "next" filter + is the job of special ``leaf'' filters call output modules. They are + implemented just like a regular filter, but they don't have a ``next'' filter to pass the time on down to. Instead, they are the end of the line and must do something with the item that results in the user seeing something on their screen or in a file. @@ -7151,14 +7653,14 @@ Those tiers are: This creates the global scope object, performs error reporting, and handles command-line options which must precede even the creation of the global - scope, such as --debug. + scope, such as @code{--debug}. @end itemize And that's Ledger in a nutshell. All the rest are details, such as which value expressions each journal item exposes, how many filters currently exist, which options the report and session scopes define, etc. -@node Journal File Format, , Internal Design, Ledger for Developers +@node Journal File Format, Developer Commands, Internal Design, Ledger for Developers @section Journal File Format for Developers This chapter offers a complete description of the journal data format, @@ -7187,12 +7689,12 @@ useful to follow standard accounting practice (@pxref{Principles of Accounting}). Since an amount is missing from the second posting, it is assumed to -be the inverse of the first. This guarantee the cardinal rule of +be the inverse of the first. This guarantees the cardinal rule of double-entry accounting: the sum of every transaction must balance to zero, or it is in error. Whenever Ledger encounters a @dfn{null posting} in a transaction, it uses it to balance the remainder. -It is also typical---though not enforced---to think of the first +It is also typical, though not enforced, to think of the first posting as the destination, and the final as the source. Thus, the amount of the first posting is typically positive. Consider: @@ -7216,7 +7718,7 @@ amount of the first posting is typically positive. Consider: @node Comments and meta-data, Specifying Amounts, Journal File Format, Journal File Format @subsection Comments and meta-data -Comments are generally started using a ';'. However, in order to +Comments are generally started using a @code{;}. However, in order to increase compatibility with other text manipulation programs and methods three additional comment characters are valid if used at the beginning of a line: @code{#}, @code{|}, and @code{*}. @@ -7259,13 +7761,13 @@ now, a word must be said about how Ledger stores numbers. Every number parsed by Ledger is stored internally as an infinite-precision rational value. Floating-point math is never used, as it cannot be trusted to maintain precision of values. So, in the -case of @samp{1000.00} above, the internal value is @samp{100000/100}. +case of @code{1000.00} above, the internal value is @code{100000/100}. While rational numbers are great at not losing precision, the question -arises: How should they be displayed? A number like @samp{100000/100} +arises: How should they be displayed? A number like @code{100000/100} is no problem, since it represents a clean decimal fraction. But what -about when the number @samp{1/1} is divided by three? How should one -print @samp{1/3}, an infinitely repeating decimal? +about when the number @code{1/1} is divided by three? How should one +print @code{1/3}, an infinitely repeating decimal? Ledger gets around this problem by rendering rationals into decimal at the last possible moment, and only for display. As such, some @@ -7276,11 +7778,11 @@ rarely, but even then it does not reflect adjustment of the @emph{internal amount}, only the displayed amount. What has still not been answered is how Ledger rounds values. Should -@samp{1/3} be printed as @samp{0.33} or @samp{0.33333}? For +@code{1/3} be printed as @code{0.33} or @code{0.33333}? For commoditized amounts, the number of decimal places is decided by observing how each commodity is used; but in the case of integer amounts, an arbitrary factor must be chosen. Initially, this factor -is six. Thus, @samp{1/3} is printed back as @samp{0.333333}. +is six. Thus, @code{1/3} is printed back as @code{0.333333}. Further, this rounding factor becomes associated with each particular value, and is carried through mathematical operations. For example, if that particular number were multiplied by itself, the decimal @@ -7303,10 +7805,10 @@ characters are allowed in a commodity name, except for the following: @itemize @bullet @item Any kind of white-space @item Numerical digits -@item Punctuation: @samp{.,;:?!} -@item Mathematical and logical operators: @samp{-+*/^&|=} -@item Bracketing characters: @samp{<>[]()}@{@} -@item The at symbol: @samp{@@} +@item Punctuation: @code{.,;:?!} +@item Mathematical and logical operators: @code{-+*/^&|=} +@item Bracketing characters: @code{<>[]()}@{@} +@item The at symbol: @code{@@} @end itemize And yet, any of these may appear in a commodity name if it is @@ -7322,9 +7824,9 @@ part is the commodity. Another feature of commoditized amounts is that they are reported back in the same form as parsed. If you specify dollar amounts using -@samp{$100}, they will print the same; likewise with @samp{100 $} or -@samp{$100.000}. You may even use decimal commas, such as -@samp{$100,00}, or thousand-marks, as in @samp{$10,000.00}. +@code{$100}, they will print the same; likewise with @code{100 $} or +@code{$100.000}. You may even use decimal commas, such as +@code{$100,00}, or thousand-marks, as in @code{$10,000.00}. These display characteristics become associated with the commodity, with the result being that all amounts of the same commodity are reported @@ -7388,7 +7890,7 @@ postings are involved: Assets:Checking @end smallexample -Here the implied cost is @samp{$57.00}, which is entered into the null +Here the implied cost is @code{$57.00}, which is entered into the null posting automatically so that the transaction balances. @node Primary commodities, , Posting costs, Journal File Format @@ -7416,7 +7918,7 @@ on the placement of the commodity in the transaction: @end smallexample The only case where knowledge of primary versus secondary comes into -play is in reports that use the @option{-V} or @option{-B} options. +play is in reports that use the @code{-V} or @code{-B} options. With these, only primary commodities are shown. If a transaction uses only one commodity, this commodity is also @@ -7424,170 +7926,262 @@ considered a primary. In fact, when Ledger goes about ensures that all transactions balance to zero, it only ever asks this of primary commodities. -@node Extending with Python, Major Changes from version 2.6, Ledger for Developers, Top -@chapter Extending with Python -Python can be used to extend your Ledger -experience. But first, a word must be said about Ledger's data model, so that -other things make sense later. - +@node Developer Commands, Ledger Development Environment, Journal File Format, Ledger for Developers +@section Developer Commands @menu -* Basic data traversal:: -* Raw vs. Cooked:: -* Queries:: -* Embedded Python:: -* Amounts:: +* echo:: +* reload:: +* source:: +* Debug Options:: +* Pre-commands:: @end menu -@node Basic data traversal, Raw vs. Cooked, Extending with Python, Extending with Python -@section Basic data traversal +@node echo, reload, Developer Commands, Developer Commands +@subsection @command{echo} +This command simply echos its argument back to the output. -Every interaction with Ledger happens in the context of a Session. Even if -you don't create a session manually, one is created for you by the top-level -interface functions. The Session is where objects live like the Commodity's -that Amount's refer to. -The make a Session useful, you must read a Journal into it, using the function -`@samp{read_journal}`. This reads Ledger data from the given file, populates a -Journal object within the current Session, and returns a reference to that -Journal object. +@node reload, source, echo, Developer Commands +@subsection @command{reload} +Forces ledger to reload any journal files. This function exists to +support external programs controlling a running ledger process and does +nothing for a command line user. -Within the Journal live all the Transaction's, Posting's, and other objects -related to your data. There are also AutomatedTransaction's and -PeriodicTransaction's, etc. +@node source, Debug Options, reload, Developer Commands +@subsection @command{source} +The @code{source} command take a journal file as an argument and parses +it checking for errors, no other reports are generated, and no other +arguments are necessary. Ledger will return success if no errors are +found. -Here is how you would traverse all the postings in your data file: -@smallexample +@node Debug Options, Pre-commands, source, Developer Commands +@subsection Debug Options - import ledger +These options are primarily for Ledger developers, but may be of some +use to a user trying something new. - for xact in ledger.read_journal("sample.dat").xacts: - for post in xact.posts: - print "Transferring %s to/from %s" % (post.amount, post.account) -@end smallexample +@table @code + @item --args-only +ignore init +files and environment variables for the ledger run. -@node Raw vs. Cooked, Queries, Basic data traversal, Extending with Python -@section Raw vs. Cooked +@item --verify +enable additional assertions during run-time. This causes a significant +slowdown. When combined with @code{--debug} ledger will produce +memory trace information. -Ledger data exists in one of two forms: raw and cooked. Raw objects are what -you get from a traversal like the above, and represent exactly what was seen -in the data file. Consider this journal: +@item --debug "argument" +If Ledger has been built with debug options this will provide extra data +during the run. The following are the available arguments to debug: -@smallexample - = true - (Assets:Cash) $100 +@multitable @columnfractions .32 .43 .27 +@item @code{account.display} @tab @code{expr.calc.when} @tab @code{org.next_amount} +@item @code{accounts.sorted} @tab @code{expr.compile} @tab @code{org.next_total} +@item @code{amount.convert} @tab @code{filters.changed_value} @tab @code{parser.error} +@item @code{amount.is_zero} @tab @code{filters.changed_value.rounding} @tab @code{pool.commodities} +@item @code{amount.parse} @tab @code{filters.collapse} @tab @code{post.assign} +@item @code{amount.price} @tab @code{filters.forecast} @tab @code{python.init} +@item @code{amount.truncate} @tab @code{filters.revalued} @tab @code{python.interp} +@item @code{amount.unround} @tab @code{format.abbrev} @tab @code{query.mask} +@item @code{amounts.commodities} @tab @code{format.expr} @tab @code{report.predicate} +@item @code{amounts.refs} @tab @code{generate.post} @tab @code{scope.symbols} +@item @code{archive.journal} @tab @code{generate.post.string} @tab @code{textual.include} +@item @code{auto.columns} @tab @code{item.meta} @tab @code{textual.parse} +@item @code{budget.generate} @tab @code{ledger.read} @tab @code{timelog} +@item @code{commodity.annotated.strip} @tab @code{ledger.validate} @tab @code{times.epoch} +@item @code{commodity.annotations} @tab @code{lookup} @tab @code{times.interval} +@item @code{commodity.compare} @tab @code{lookup.account} @tab @code{times.parse} +@item @code{commodity.download} @tab @code{mask.match} @tab @code{value.sort} +@item @code{commodity.prices.add} @tab @code{memory.counts} @tab @code{value.storage.refcount} +@item @code{commodity.prices.find} @tab @code{memory.counts.live} @tab @code{xact.extend} +@item @code{convert.csv} @tab @code{memory.debug} @tab @code{xact.extend.cleared} +@item @code{csv.mappings} @tab @code{op.cons} @tab @code{xact.extend.fail} +@item @code{csv.parse} @tab @code{op.memory} @tab @code{xact.finalize} +@item @code{draft.xact} @tab @code{option.args} +@item @code{expr.calc} @tab @code{option.names} +@end multitable - 2012-03-01 KFC - Expenses:Food $100 - Assets:Credit -@end smallexample +@item --trace INTEGER_TRACE_LEVEL +Enable tracing. The integer specifies the level of trace desired: +@multitable @columnfractions .3 .7 +@item @code{LOG_OFF} @tab 0 +@item @code{LOG_CRIT} @tab 1 +@item @code{LOG_FATAL} @tab 2 +@item @code{LOG_ASSERT} @tab 3 +@item @code{LOG_ERROR} @tab 4 +@item @code{LOG_VERIFY} @tab 5 +@item @code{LOG_WARN} @tab 6 +@item @code{LOG_INFO} @tab 7 +@item @code{LOG_EXCEPT} @tab 8 +@item @code{LOG_DEBUG} @tab 9 +@item @code{LOG_TRACE} @tab 10 +@item @code{LOG_ALL} @tab 11 +@end multitable +@item --verbose +Print detailed information on the execution of Ledger. -In this case, the @emph{raw} regular transaction in this file is: +@item --version +Print version information and exit. +@end table +@node Pre-commands, , Debug Options, Developer Commands +@subsection Pre-Commands +Pre-commands are useful when you aren't sure how a command or option +will work. +@table @code +@item args +evaluate the given arguments against the following model transaction: @smallexample - 2012-03-01 KFC - Expenses:Food $100 - Assets:Credit +2004/05/27 Book Store + ; This note applies to all postings. :SecondTag: + Expenses:Books 20 BOOK @@ $10 + ; Metadata: Some Value + ; Typed:: $100 + $200 + ; :ExampleTag: + ; Here follows a note describing the posting. + Liabilities:MasterCard $-200.00 @end smallexample - -While the @emph{cooked} form is: - +@item eval +evaluate the given value expression against the model transaction +@item expr "LIMIT EXPRESSION" +Print details of how ledger parses the given limit expression and apply +it against a model transaction. +@item format "FORMATTING" +Print details of how ledger uses the given formatting description and +apply it against a model transaction. +@item generate +Randomly generates syntactically valid Ledger data from a seed. Used by the +GenerateTests harness for development testing +@item parse <VALUE EXPR> +Print details of how ledger uses the given value expression description +and apply it against a model transaction. +@item period +evaluate the given period and report how Ledger interprets it: @smallexample - 2012-03-01 KFC - Expenses:Food $100 - Assets:Credit $-100 - (Assets:Cash) $100 -@end smallexample +20:22:21 ~/ledger (next)> ledger period "this year" +--- Period expression tokens --- +TOK_THIS: this +TOK_YEAR: year +END_REACHED: <EOF> -So the easy way to think about raw vs. cooked is that raw is the unprocessed -data, and cooked has had all considerations applied. +--- Before stabilization --- + range: in year 2011 -When you traverse a Journal by iterating its transactions, you are generally -looking at raw data. In order to look at cooked data, you must generate a -report of some kind by querying the journal: +--- After stabilization --- + range: in year 2011 + start: 11-Jan-01 + finish: 12-Jan-01 -@smallexample - for post in ledger.read_journal("sample.dat").query("food"): - print "Transferring %s to/from %s" % (post.amount, post.account) +--- Sample dates in range (max. 20) --- + 1: 11-Jan-01 @end smallexample - -The reason why queries iterate over postings instead of transactions is that -queries often return only a ``slice'' of the transactions they apply to. You -can always get at a matching posting's transaction by looking at its "xact" -member: +@item query +evaluate the given query and report how Ledger interprets it against the +model transaction: @smallexample - last_xact = None - for post in ledger.read_journal("sample.dat").query(""): - if post.xact != last_xact: - for post in post.xact.posts: - print "Transferring %s to/from %s" % (post.amount, - post.account) - last_xact = post.xact -@end smallexample +20:25:42 ~/ledger (next)> ledger query "/Book/" +--- Input arguments --- +("/Book/") -This query ends up reporting every cooked posting in the Journal, but does it -transaction-wise. It relies on the fact that an unsorted report returns -postings in the exact order they were parsed from the journal file. +--- Context is first posting of the following transaction --- +2004/05/27 Book Store + ; This note applies to all postings. :SecondTag: + Expenses:Books 20 BOOK @@ $10 + ; Metadata: Some Value + ; Typed:: $100 + $200 + ; :ExampleTag: + ; Here follows a note describing the posting. + Liabilities:MasterCard $-200.00 -@node Queries, Embedded Python, Raw vs. Cooked, Extending with Python -@section Queries +--- Input expression --- +(account =~ /Book/) -The Journal.query() method accepts every argument you can specify on the -command-line, including --options. +--- Text as parsed --- +(account =~ /Book/) -Since a query ``cooks'' the journal it applies to, only one query may be active -for that journal at a given time. Once the query object is gone (after the -for loop), then the data reverts back to its raw state. +--- Expression tree --- +0x7fd639c0da40 O_MATCH (1) +0x7fd639c10170 IDENT: account (1) +0x7fd639c10780 VALUE: /Book/ (1) -@node Embedded Python, Amounts, Queries, Extending with Python -@section Embedded Python +--- Compiled tree --- +0x7fd639c10520 O_MATCH (1) +0x7fd639c0d6c0 IDENT: account (1) +0x7fd639c0d680 FUNCTION (1) +0x7fd639c10780 VALUE: /Book/ (1) -Can you embed Python into your data files using the 'python' directive: +--- Calculated value --- +true +@end smallexample +@item template +Shows the insertion template that a @code{draft} or @code{xact} sub-command generates. +This is a debugging command. +@end table -@smallexample - python - import so - def check_path(path_value): - print "%s => %s" % (str(path_value), os.path.isfile(str(path_value))) - return os.path.isfile(str(path_value)) +@node Ledger Development Environment, , Developer Commands, Ledger for Developers +@section Ledger Development Environment - tag PATH - assert check_path(value) +@menu +* acrep build configuration tool:: +* Testing Framework:: +@end menu - 2012-02-29 KFC - ; PATH: somebogusfile.dat - Expenses:Food $20 - Assets:Cash -@end smallexample +@node acrep build configuration tool, Testing Framework, Ledger Development Environment, Ledger Development Environment +@subsection @code{acprep} build configuration tool -Any Python functions you define this way become immediately available as -valexpr functions. +@node Testing Framework, , acrep build configuration tool, Ledger Development Environment +@subsection Testing Framework +Ledger source ships with a farily complete set of tests to verify that +all is well, and no old errors have been resurfaced. Tests are run +individually with @code{ctest}. All tests can be run using @code{make +check} or @code{ninja check} depending on which build tool you prefer. -@node Amounts, , Embedded Python, Extending with Python -@section Amounts +Once built, the ledger executable resides under the @file{build} +subdirectory in the source tree. Tests are built and stored in the test +subdirectory for the build. For example, +@file{~/ledger/build/ledger/opt/test}. -When numbers come from Ledger, like post.amount, the type of the value is -Amount. It can be used just like an ordinary number, except that addition -and subtraction are restricted to amounts with the same commodity. If you -need to create sums of multiple commodities, use a Balance. For example: +@menu +* Running Tests:: +* Writing Tests:: +@end menu -@smallexample - total = Balance() - for post in ledger.read_journal("sample.dat").query(""): - total += post.amount - print total -@end smallexample +@node Running Tests, Writing Tests, Testing Framework, Testing Framework +@subsubsection Running Tests + +The complete test sweet can be run from the build directory using the +check option for the build tool you use. For example, @code{make +check}. The entire test suit tast around a minute for the optimized +built and many times longer for the debug version. While developing and +debugging, running individual tests can save a great deal of time. + +Individual tests can be run fron the @file{test} subdirectory of the +build location. To execute a single test use @code{ctest -V -R regex}, +where the regex mathes the name of the test you want to build. -@node Major Changes from version 2.6, Example Data File, Extending with Python, Top +There are nearly 300 tests stored under the @file{test} sudirectoro +tmain source distribution. They are broken into two broad categories, +baseline and regression. To run the @file{5FBF2ED8} test, for example, +issue @code{ctest -V -R "5FB"}. +@node Writing Tests, , Running Tests, Testing Framework +@subsubsection Writing Tests + +@node Major Changes from version 2.6, Example Data File, Ledger for Developers, Top @chapter Major Changes from version 2.6 +@itemize +@item OFX support has been removed from Ledger 3.0 +@item single character value expressions are deprecated and should be changed to the new value expressions available in 3.0 +@end itemize + @node Example Data File, Miscellaneous Notes, Major Changes from version 2.6, Top @appendix Example Journal File: drewr.dat The following journal file is included with the source distribution of ledger. It is called @file{drewr.dat} and exhibits many ledger - features, include automatic and virtual transactions, + features, include automatic and virtual transactions, @smallexample ; -*- ledger -*- @@ -7690,6 +8284,7 @@ ledger register Checking --sort d -d 'd>[2011/04/01]' until 2011/05/25 (Liabilities:Tithe Owed) -1.0 @end smallexample + @node Concept Index, Command Index, Miscellaneous Notes, Top @unnumbered Concept Index diff --git a/lib/Makefile b/lib/Makefile index cb05e44d..ddbe0585 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -2,11 +2,11 @@ # This is only important if you intend to produce a Ledger binary for # installation. -STOW_ROOT = /usr/local/stow +STOW_ROOT = /usr/local/Cellar/boost PRODUCTS = $(HOME)/Products GCC_VERSION = 4.7 -BOOST_VERSION = 1_49_0 +BOOST_VERSION = 1_52_0 CC = gcc-mp-$(GCC_VERSION) ifeq ($(CC),clang) @@ -27,7 +27,7 @@ BOOST_SOURCE = boost-release ifeq ($(GCC_VERSION),4.7) BOOST_DEFINES = define=_GLIBCXX__PTHREADS=1 else -BOOST_DEFINES = +BOOST_DEFINES = endif ifeq ($(CC),clang) BOOST_TOOLSET = clang @@ -37,14 +37,10 @@ endif BOOST_FLAGS = toolset=$(BOOST_TOOLSET) --layout=versioned \ link=shared threading=single $(BOOST_DEFINES) BOOST_DIR = boost_$(BOOST_VERSION)-$(DIR_SUFFIX) -BOOST_STOW = $(STOW_ROOT)/$(BOOST_DIR) +BOOST_STOW = $(STOW_ROOT)/$(BOOST_VERSION) BOOST_BUILD = $(PRODUCTS)/$(BOOST_DIR) -ICU_FLAGS = -sHAVE_ICU=1 -sICU_PATH=$(STOW_ROOT)/icu-$(DIR_SUFFIX) -BOOST_ICU_DIR = boost_$(BOOST_VERSION)-icu-$(DIR_SUFFIX) -BOOST_ICU_STOW = $(STOW_ROOT)/$(BOOST_ICU_DIR) -BOOST_ICU_BUILD = $(PRODUCTS)/$(BOOST_ICU_DIR) -all: boost-build #icu-build boost-icu-build +all: boost-build prepare-boost: perl -i -pe 's/local command = \[ common\.get-invocation-command darwin : g\+\+ : .*/local command = [ common.get-invocation-command darwin : g++ : $(CXX) ] ;/;' $(BOOST_SOURCE)/tools/build/v2/tools/darwin.jam @@ -56,29 +52,5 @@ boost-build: prepare-boost ./b2 $(OPTJ) debug release --prefix=$(BOOST_STOW) \ --build-dir=$(BOOST_BUILD) $(BOOST_FLAGS) install) -icu-build: - -(cd icu/source; make distclean) - (cd icu/source; sh autogen.sh; \ - ./configure CPPFLAGS="$(CPPFLAGS)" \ - CFLAGS="$(CFLAGS)" \ - LDFLAGS="$(LDFLAGS)" \ - CC="$(CC)" CXX="$(CXX)" LD="$(LD)" \ - --enable-static --enable-debug \ - --prefix=$(STOW_ROOT)/icu-$(DIR_SUFFIX) && \ - make install) - -boost-icu-build: - (cd $(BOOST_SOURCE) && \ - sh bootstrap.sh && \ - ./bjam $(OPTJ) debug --prefix=$(BOOST_ICU_STOW) \ - --build-dir=$(BOOST_ICU_BUILD) \ - $(BOOST_FLAGS) $(ICU_FLAGS) install) - clean: -rm -fr $(BOOST_STOW) $(BOOST_BUILD) - -rm -fr $(BOOST_ICU_STOW) $(BOOST_ICU_BUILD) - -rm -fr $(STOW_ROOT)/icu-$(DIR_SUFFIX) - -(cd icu/source; make distclean) - -lib-clean: - -(cd icu/source; make distclean) diff --git a/lib/build.sh b/lib/build.sh index 28408d73..4f3f2e7f 100755 --- a/lib/build.sh +++ b/lib/build.sh @@ -4,14 +4,14 @@ # clang-3.1 from MacPorts. I build my own Boost instead of using MacPorts' # Boost in order to get better debugging support, and to link with libc++. -export PATH=$PATH:/opt/local/lib/openmpi/bin +#export PATH=$PATH:/opt/local/lib/openmpi/bin cat > ~/user-config.jam <<EOF -using clang-darwin : : "/usr/local/bin/clang++" : <cxxflags>-std=c++11 <include>/usr/local/include ; +using clang-darwin : : "/usr/local/bin/clang++" : <cxxflags>-std=c++11 ; EOF # jww (2012-04-24): This is still linking against /usr/lib/libc++.1.dylib # instead of /usr/local/lib/libc++.1.dylib -make CXX=clang++ LD=clang++ CC=clang OPTJ=-j20 \ +make CXX=clang++ LD=clang++ CC=clang OPTJ=-j20 \ BOOST_TOOLSET=clang-darwin DIR_SUFFIX=clang31 \ - BOOST_DEFINES="-sICU_PATH=/usr/local cxxflags=\"-g -std=c++11 -nostdlibinc -I/usr/local/include -I/usr/local/include/c++/v1 -I/opt/local/include -I/usr/include -stdlib=libc++\" linkflags=\"-g -Z -L/usr/local/lib -L/opt/local/lib -L/usr/lib /usr/local/lib/libc++.dylib -stdlib=libc++\"" + BOOST_DEFINES="-sHAVE_ICONV=1 -sHAVE_ICU=1 -sICU_PATH=/usr/local/opt/icu4c cxxflags=\"-g -std=c++11 $* -nostdlibinc -isystem /usr/local/include -isystem /usr/local/include/c++/v1 -isystem /usr/include -stdlib=libc++\" linkflags=\"-g $* -L/usr/local/lib -L/usr/lib /usr/local/lib/libc++.dylib -stdlib=libc++\"" diff --git a/lisp/CMakeLists.txt b/lisp/CMakeLists.txt index 949171b3..876b3548 100644 --- a/lisp/CMakeLists.txt +++ b/lisp/CMakeLists.txt @@ -1,19 +1,21 @@ set(EMACS_LISP_SOURCES + ldg-commodities.el ldg-complete.el ldg-exec.el + ldg-fonts.el + ldg-init.el ldg-mode.el ldg-new.el + ldg-occur.el ldg-post.el ldg-reconcile.el ldg-regex.el - ldg-register.el ldg-report.el + ldg-sort.el ldg-state.el ldg-test.el ldg-texi.el - ldg-xact.el - ledger.el - timeclock.el) + ldg-xact.el) # find emacs and complain if not found find_program(EMACS_EXECUTABLE emacs) diff --git a/lisp/ldg-commodities.el b/lisp/ldg-commodities.el new file mode 100644 index 00000000..031bddeb --- /dev/null +++ b/lisp/ldg-commodities.el @@ -0,0 +1,144 @@ +;;; ldg-commodities.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Helper functions to deal with commoditized numbers. A commoditized +;; number will be a list of value and string where the string contains +;; the commodity + +;;; Code: + +(require 'ldg-regex) + +(defcustom ledger-reconcile-default-commodity "$" + "The default commodity for use in target calculations in ledger reconcile." + :type 'string + :group 'ledger-reconcile) + +(defun ledger-split-commodity-string (str) + "Split a commoditized string, STR, into two parts. +Returns a list with (value commodity)." + (if (> (length str) 0) + (let ((number-regex (if (assoc "decimal-comma" ledger-environment-alist) + ledger-amount-decimal-comma-regex + ledger-amount-decimal-period-regex))) + (with-temp-buffer + (insert str) + (goto-char (point-min)) + (cond + ((re-search-forward "\"\\(.*\\)\"" nil t) ; look for quoted commodities + (let ((com (delete-and-extract-region + (match-beginning 1) + (match-end 1)))) + (if (re-search-forward number-regex nil t) + (list + (string-to-number + (ledger-commodity-string-number-decimalize + (delete-and-extract-region (match-beginning 0) (match-end 0)) :from-user)) + com)))) + ((re-search-forward number-regex nil t) + ;; found a number in the current locale, return it in + ;; the car. Anything left over is annotation, + ;; the first thing should be the commodity, separated + ;; by whitespace, return it in the cdr. I can't think of any + ;; counterexamples + (list + (string-to-number + (ledger-commodity-string-number-decimalize + (delete-and-extract-region (match-beginning 0) (match-end 0)) :from-user)) + (nth 0 (split-string (buffer-substring-no-properties (point-min) (point-max)))))) + ((re-search-forward "0" nil t) + ;; couldn't find a decimal number, look for a single 0, + ;; indicating account with zero balance + (list 0 ledger-reconcile-default-commodity))))) + ;; nothing found, return 0 + (list 0 ledger-reconcile-default-commodity))) + +(defun ledger-string-balance-to-commoditized-amount (str) + "Return a commoditized amount (val, 'comm') from STR." + (let ((fields (split-string str "[\n\r]"))) ; break any balances + ; with multi commodities + ; into a list + (mapcar #'(lambda (str) + (ledger-split-commodity-string str)) + fields))) + +(defun -commodity (c1 c2) + "Subtract C2 from C1, ensuring their commodities match." + (if (string= (cadr c1) (cadr c2)) + (list (- (car c1) (car c2)) (cadr c1)) + (error "Can't subtract different commodities %S from %S" c2 c1))) + +(defun +commodity (c1 c2) + "Add C1 and C2, ensuring their commodities match." + (if (string= (cadr c1) (cadr c2)) + (list (+ (car c1) (car c2)) (cadr c1)) + (error "Can't add different commodities, %S to %S" c1 c2))) + +(defun ledger-commodity-string-number-decimalize (number-string direction) + "Take NUMBER-STRING and ensure proper decimalization for use by string-to-number and number-to-string. + +DIRECTION can be :to-user or :from-user. All math calculations +are done with decimal-period, some users may prefer decimal-comma +which must be translated both directions." + (let ((val number-string)) + (if (assoc "decimal-comma" ledger-environment-alist) + (cond ((eq direction :from-user) + ;; change string to decimal-period + (while (string-match "," val) + (setq val (replace-match "." nil nil val)))) ;; switch to period separator + ((eq direction :to-user) + ;; change to decimal-comma + (while (string-match "\\." val) + (setq val (replace-match "," nil nil val)))) ;; gets rid of periods + (t + (error "ledger-commodity-string-number-decimalize: direction not properly specified %S" direction))) + (while (string-match "," val) + (setq val (replace-match "" nil nil val)))) + val)) + + + +(defun ledger-commodity-to-string (c1) + "Return string representing C1. +Single character commodities are placed ahead of the value, +longer ones are after the value." +(let ((val (ledger-commodity-string-number-decimalize + (number-to-string (car c1)) :to-user)) + (commodity (cadr c1))) + (if (> (length commodity) 1) + (concat val " " commodity) + (concat commodity " " val)))) + +(defun ledger-read-commodity-string (prompt) + (let ((str (read-from-minibuffer + (concat prompt " (" ledger-reconcile-default-commodity "): "))) + comm) + (if (> (length str) 0) + (progn + (setq comm (ledger-split-commodity-string str)) + (if (cadr comm) + comm + (list (car comm) ledger-reconcile-default-commodity)))))) + +(provide 'ldg-commodities) + +;;; ldg-commodities.el ends here diff --git a/lisp/ldg-complete.el b/lisp/ldg-complete.el index 7b4b0471..bd907bc8 100644 --- a/lisp/ldg-complete.el +++ b/lisp/ldg-complete.el @@ -1,29 +1,41 @@ -;;(require 'esh-util) -;;(require 'esh-arg) +;;; ldg-complete.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Functions providing payee and account auto complete. + (require 'pcomplete) ;; 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) - ((looking-at "^\\(sun\\|mon\\|tue\\|wed\\|thu\\|fri\\|sat\\)\\s-+") - (goto-char (match-end 0)) - 'entry) - (t - (ignore (goto-char here)))))) +;;; Code: (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)) + ;; this is more complex than it appears to need, so that it can work + ;; with pcomplete. See pcomplete-parse-arguments-function for + ;; details + (let* ((begin (save-excursion + (ledger-thing-at-point) ;; leave point at beginning of thing under point + (point))) (end (point)) begins args) (save-excursion @@ -36,115 +48,126 @@ args))) (cons (reverse args) (reverse begins))))) -(defun ledger-entries () + +(defun ledger-payees-in-buffer () + "Scan buffer and return list of all payees." (let ((origin (point)) - entries-list) + payees-list) (save-excursion (goto-char (point-min)) (while (re-search-forward - (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" - "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) + ledger-payee-any-status-regex nil t) ;; matches first line (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)))) + (setq payees-list (cons (match-string-no-properties 3) + payees-list))))) ;; add the payee + ;; to the list + (pcomplete-uniqify-list (nreverse payees-list)))) -(defvar ledger-account-tree nil) - -(defun ledger-find-accounts () - (let ((origin (point)) account-path elements) +(defun ledger-find-accounts-in-buffer () + "Search through buffer and build tree of accounts. +Return tree structure" + (let ((origin (point)) + (account-tree (list t)) + (account-elements nil)) (save-excursion - (setq ledger-account-tree (list t)) (goto-char (point-min)) (while (re-search-forward - "^[ \t]+\\([*!]\\s-+\\)?[[(]?\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)" nil t) + ledger-account-any-status-regex 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))))))))) + (setq account-elements + (split-string + (match-string-no-properties 2) ":")) + (let ((root account-tree)) + (while account-elements + (let ((xact (assoc (car account-elements) root))) + (if xact + (setq root (cdr xact)) + (setq xact (cons (car account-elements) (list t))) + (nconc root (list xact)) + (setq root (cdr xact)))) + (setq account-elements (cdr account-elements))))))) + account-tree)) (defun ledger-accounts () - (ledger-find-accounts) + "Return a tree of all accounts in the buffer." (let* ((current (caar (ledger-parse-arguments))) (elements (and current (split-string current ":"))) - (root ledger-account-tree) + (root (ledger-find-accounts-in-buffer)) (prefix nil)) (while (cdr elements) - (let ((entry (assoc (car elements) root))) - (if entry + (let ((xact (assoc (car elements) root))) + (if xact (setq prefix (concat prefix (and prefix ":") (car elements)) - root (cdr entry)) - (setq root nil elements nil))) + root (cdr xact)) + (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)))) + (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" + "Do appropriate completion for the thing at point." (interactive) (while (pcomplete-here (if (eq (save-excursion - (ledger-thing-at-point)) 'entry) + (ledger-thing-at-point)) 'transaction) (if (null current-prefix-arg) - (ledger-entries) ; this completes against entry names - (progn - (let ((text (buffer-substring (line-beginning-position) - (line-end-position)))) - (delete-region (line-beginning-position) - (line-end-position)) - (condition-case err - (ledger-add-entry text t) - ((error) - (insert text)))) - (forward-line) - (goto-char (line-end-position)) - (search-backward ";" (line-beginning-position) t) - (skip-chars-backward " \t0123456789.,") - (throw 'pcompleted t))) - (ledger-accounts))))) - -(defun ledger-fully-complete-entry () - "Do appropriate completion for the thing at point" + (ledger-payees-in-buffer) ;; this completes against payee names + (progn + (let ((text (buffer-substring-no-properties (line-beginning-position) + (line-end-position)))) + (delete-region (line-beginning-position) + (line-end-position)) + (condition-case nil + (ledger-add-transaction text t) + (error nil))) + (forward-line) + (goto-char (line-end-position)) + (search-backward ";" (line-beginning-position) t) + (skip-chars-backward " \t0123456789.,") + (throw 'pcompleted t))) + (ledger-accounts))))) + +(defun ledger-fully-complete-xact () + "Completes a transaction if there is another matching payee in the buffer. +Does not use ledger xact" (interactive) - (let ((name (caar (ledger-parse-arguments))) + (let* ((name (caar (ledger-parse-arguments))) + (rest-of-name name) xacts) (save-excursion - (when (eq 'entry (ledger-thing-at-point)) + (when (eq 'transaction (ledger-thing-at-point)) + (delete-region (point) (+ (length name) (point))) + ;; Search backward for a matching payee (when (re-search-backward - (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" - (regexp-quote name) "\\(\t\\|\n\\| [ \t]\\)") nil t) - (forward-line) - (while (looking-at "^\\s-+") + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+\\(.*" + (regexp-quote name) ".*\\)" ) nil t) + (setq rest-of-name (match-string 3)) + ;; Start copying the postings + (forward-line) + (while (looking-at ledger-account-any-status-regex) (setq xacts (cons (buffer-substring-no-properties (line-beginning-position) (line-end-position)) xacts)) (forward-line)) (setq xacts (nreverse xacts))))) + ;; Insert rest-of-name and the postings (when xacts (save-excursion - (insert ?\n) + (insert rest-of-name ?\n) (while xacts (insert (car xacts) ?\n) (setq xacts (cdr xacts)))) @@ -153,4 +176,51 @@ (if (re-search-backward "\\(\t\\| [ \t]\\)" nil t) (goto-char (match-end 0)))))) + +(defun ledger-pcomplete (&optional interactively) + "Complete rip-off of pcomplete from pcomplete.el, only added +ledger-magic-tab in the previous commands list so that +ledger-magic-tab would cycle properly" + (interactive "p") + (if (and interactively + pcomplete-cycle-completions + pcomplete-current-completions + (memq last-command '(ledger-magic-tab + ledger-pcomplete + pcomplete-expand-and-complete + pcomplete-reverse))) + (progn + (delete-backward-char pcomplete-last-completion-length) + (if (eq this-command 'pcomplete-reverse) + (progn + (push (car (last pcomplete-current-completions)) + pcomplete-current-completions) + (setcdr (last pcomplete-current-completions 2) nil)) + (nconc pcomplete-current-completions + (list (car pcomplete-current-completions))) + (setq pcomplete-current-completions + (cdr pcomplete-current-completions))) + (pcomplete-insert-entry pcomplete-last-completion-stub + (car pcomplete-current-completions) + nil pcomplete-last-completion-raw)) + (setq pcomplete-current-completions nil + pcomplete-last-completion-raw nil) + (catch 'pcompleted + (let* ((pcomplete-stub) + pcomplete-seen pcomplete-norm-func + pcomplete-args pcomplete-last pcomplete-index + (pcomplete-autolist pcomplete-autolist) + (pcomplete-suffix-list pcomplete-suffix-list) + (completions (pcomplete-completions)) + (result (pcomplete-do-complete pcomplete-stub completions))) + (and result + (not (eq (car result) 'listed)) + (cdr result) + (pcomplete-insert-entry pcomplete-stub (cdr result) + (memq (car result) + '(sole shortest)) + pcomplete-last-completion-raw)))))) + (provide 'ldg-complete) + +;;; ldg-complete.el ends here diff --git a/lisp/ldg-context.el b/lisp/ldg-context.el new file mode 100644 index 00000000..ccaa39f2 --- /dev/null +++ b/lisp/ldg-context.el @@ -0,0 +1,210 @@ +;;; ldg-context.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Provide facilities for reflection in ledger buffers + +;;; Code: + +(eval-when-compile + (require 'cl)) + +;; *-string constants are assembled in the single-line-config macro to +;; form the regex and list of elements +(defconst indent-string "\\(^[ \t]+\\)") +(defconst status-string "\\([*! ]?\\)") +(defconst account-string "[\\[(]?\\(.*?\\)[])]?") +(defconst amount-string "[ \t]?\\(-?[0-9]+\\.[0-9]*\\)") +(defconst comment-string "[ \t]*;[ \t]*\\(.*?\\)") +(defconst nil-string "\\([ \t]+\\)") +(defconst commodity-string "\\(.+?\\)") +(defconst date-string "^\\(\\([0-9]\\{4\\}[/-]\\)?[01]?[0-9][/-][0123]?[0-9]\\)") +(defconst code-string "\\((\\(.*\\))\\)?") +(defconst payee-string "\\(.*\\)") + +(defmacro line-regex (&rest elements) + (let (regex-string) + (concat (dolist (e elements regex-string) + (setq regex-string + (concat regex-string + (eval + (intern + (concat (symbol-name e) "-string")))))) "[ \t]*$"))) + +(defmacro single-line-config2 (&rest elements) +"Take list of ELEMENTS and return regex and element list for use in context-at-point" + (let (regex-string) + `'(,(concat (dolist (e elements regex-string) + (setq regex-string + (concat regex-string + (eval + (intern + (concat (symbol-name e) "-string")))))) "[ \t]*$") + ,elements))) + +(defmacro single-line-config (&rest elements) + "Take list of ELEMENTS and return regex and element list for use in context-at-point" + `'(,(eval `(line-regex ,@elements)) + ,elements)) + +(defconst ledger-line-config + (list (list 'xact (list (single-line-config date nil status nil nil code payee comment) + (single-line-config date nil status nil nil code payee))) + (list 'acct-transaction (list (single-line-config indent comment) + (single-line-config indent status account nil commodity amount nil comment) + (single-line-config indent status account nil commodity amount) + (single-line-config indent status account nil amount nil commodity comment) + (single-line-config indent status account nil amount nil commodity) + (single-line-config indent status account nil amount) + (single-line-config indent status account nil comment) + (single-line-config indent status account))))) + +(defun ledger-extract-context-info (line-type pos) + "Get context info for current line with LINE-TYPE. + +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-thing-at-point () + "Describe thing at points. Return 'transaction, 'posting, or nil. +Leave point at the beginning of the thing under point" + (let ((here (point))) + (goto-char (line-beginning-position)) + (cond ((looking-at "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.+?)\\)?\\s-+") + (goto-char (match-end 0)) + 'transaction) + ((looking-at "^\\s-+\\([*!]\\s-+\\)?[[(]?\\(.\\)") + (goto-char (match-beginning 2)) + 'posting) + ((looking-at "^\\(sun\\|mon\\|tue\\|wed\\|thu\\|fri\\|sat\\)\\s-+") + (goto-char (match-end 0)) + 'day) + (t + (ignore (goto-char here)))))) + +(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 +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 'xact pos)) + ((equal first-char ?\=) + '(automated-xact nil nil)) + ((equal first-char ?\~) + '(period-xact 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 from 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))) + +(provide 'ldg-context) + +;;; ldg-report.el ends here diff --git a/lisp/ldg-exec.el b/lisp/ldg-exec.el index bf3565b4..f6c3bb54 100644 --- a/lisp/ldg-exec.el +++ b/lisp/ldg-exec.el @@ -1,3 +1,36 @@ +;;; ldg-exec.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Code for executing ledger synchronously. + +;;; Code: + +(defconst ledger-version-needed "3.0.0" + "The version of ledger executable needed for interactive features.") + +(defvar ledger-works nil + "Flag showing whether the ledger binary can support `ledger-mode' interactive features.") + (defgroup ledger-exec nil "Interface to the Ledger command-line accounting program." :group 'ledger) @@ -5,30 +38,64 @@ (defcustom ledger-binary-path "ledger" "Path to the ledger executable." :type 'file - :group 'ledger) + :group 'ledger-exec) + +(defun ledger-exec-handle-error (ledger-output) + "Deal with ledger errors contained in LEDGER-OUTPUT." + (with-current-buffer (get-buffer-create "*Ledger Error*") + (insert-buffer-substring ledger-output) + (view-mode) + (setq buffer-read-only t))) + +(defun ledger-exec-success-p (ledger-output-buffer) + (with-current-buffer ledger-output-buffer + (goto-char (point-min)) + (if (and (> (buffer-size) 1) (looking-at (regexp-quote "While"))) + nil ;; failure, there is an error starting with "While" + ledger-output-buffer))) (defun ledger-exec-ledger (input-buffer &optional output-buffer &rest args) - "Run Ledger." + "Run Ledger using INPUT-BUFFER and optionally capturing output in OUTPUT-BUFFER with ARGS." (if (null ledger-binary-path) - (error "The variable `ledger-binary-path' has not been set")) - (let ((buf (or input-buffer (current-buffer))) - (outbuf (or output-buffer - (generate-new-buffer " *ledger-tmp*")))) - (with-current-buffer buf - (let ((coding-system-for-write 'utf-8) - (coding-system-for-read 'utf-8)) - (apply #'call-process-region - (append (list (point-min) (point-max) - ledger-binary-path nil outbuf nil "-f" "-") - args))) - outbuf))) - -(defun ledger-exec-read (&optional input-buffer &rest args) - (with-current-buffer - (apply #'ledger-exec-ledger input-buffer nil "emacs" args) - (goto-char (point-min)) - (prog1 - (read (current-buffer)) - (kill-buffer (current-buffer))))) + (error "The variable `ledger-binary-path' has not been set") + (let ((buf (or input-buffer (current-buffer))) + (outbuf (or output-buffer + (generate-new-buffer " *ledger-tmp*")))) + (with-current-buffer buf + (let ((coding-system-for-write 'utf-8) + (coding-system-for-read 'utf-8)) + (apply #'call-process-region + (append (list (point-min) (point-max) + ledger-binary-path nil outbuf nil "-f" "-") + args))) + (if (ledger-exec-success-p outbuf) + outbuf + (ledger-exec-handle-error outbuf)))))) + +(defun ledger-version-greater-p (needed) + "Verify the ledger binary is usable for `ledger-mode' (version greater than NEEDED)." + (let ((buffer ledger-buf) + (version-strings '())) + (with-temp-buffer + (when (ledger-exec-ledger (current-buffer) (current-buffer) "--version") + (goto-char (point-min)) + (delete-horizontal-space) + (setq version-strings (split-string + (buffer-substring-no-properties (point) + (point-max)))) + (if (and (string-match (regexp-quote "Ledger") (car version-strings)) + (or (string= needed (cadr version-strings)) + (string< needed (cadr version-strings)))) + t ;; success + nil))))) ;;failure + +(defun ledger-check-version () + "Verify that ledger works and is modern enough." + (interactive) + (if (setq ledger-works (ledger-version-greater-p ledger-version-needed)) + (message "Good Ledger Version") + (message "Bad Ledger Version"))) (provide 'ldg-exec) + +;;; ldg-exec.el ends here diff --git a/lisp/ldg-fonts.el b/lisp/ldg-fonts.el new file mode 100644 index 00000000..cb7a81c0 --- /dev/null +++ b/lisp/ldg-fonts.el @@ -0,0 +1,137 @@ +;;; ldg-fonts.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; All of the faces for ledger mode are defined here. + +;;; Code: + +(require 'ldg-regex) + +(defgroup ledger-faces nil "Ledger mode highlighting" :group 'ledger) +(defface ledger-font-payee-uncleared-face + `((t :foreground "#dc322f" :weight bold )) + "Default face for Ledger" + :group 'ledger-faces) + +(defface ledger-font-payee-cleared-face + `((t :foreground "#657b83" :weight normal )) + "Default face for cleared (*) transactions" + :group 'ledger-faces) + +(defface ledger-font-xact-highlight-face + `((t :background "#eee8d5")) + "Default face for transaction under point" + :group 'ledger-faces) + +(defface ledger-font-pending-face + `((t :foreground "#cb4b16" :weight normal )) + "Default face for pending (!) transactions" + :group 'ledger-faces) + +(defface ledger-font-other-face + `((t :foreground "#657b83" )) + "Default face for other transactions" + :group 'ledger-faces) + +(defface ledger-font-posting-account-face + `((t :foreground "#268bd2" )) + "Face for Ledger accounts" + :group 'ledger-faces) + +(defface ledger-font-posting-account-cleared-face + `((t :foreground "#657b83" )) + "Face for Ledger accounts" + :group 'ledger-faces) + +(defface ledger-font-posting-account-pending-face + `((t :foreground "#cb4b16" )) + "Face for Ledger accounts" + :group 'ledger-faces) + +(defface ledger-font-posting-amount-face + `((t :foreground "#cb4b16" )) + "Face for Ledger amounts" + :group 'ledger-faces) + +(defface ledger-occur-narrowed-face + `((t :foreground "grey70" :invisible t )) + "Default face for Ledger occur mode hidden transactions" + :group 'ledger-faces) + +(defface ledger-occur-xact-face + `((t :background "#eee8d5" )) + "Default face for Ledger occur mode shown transactions" + :group 'ledger-faces) + +(defface ledger-font-comment-face + `((t :foreground "#93a1a1" :slant italic)) + "Face for Ledger comments" + :group 'ledger-faces) + +(defface ledger-font-reconciler-uncleared-face + `((t :foreground "#dc322f" :weight bold )) + "Default face for uncleared transactions in the reconcile window" + :group 'ledger-faces) + +(defface ledger-font-reconciler-cleared-face + `((t :foreground "#657b83" :weight normal )) + "Default face for cleared (*) transactions in the reconcile window" + :group 'ledger-faces) + +(defface ledger-font-reconciler-pending-face + `((t :foreground "#cb4b16" :weight normal )) + "Default face for pending (!) transactions in the reconcile window" + :group 'ledger-faces) + +(defface ledger-font-report-clickable-face + `((t :foreground "#cb4b16" :weight normal )) + "Default face for pending (!) transactions in the reconcile window" + :group 'ledger-faces) + + +(defvar ledger-font-lock-keywords + `( ;; (,ledger-other-entries-regex 1 + ;; ledger-font-other-face) + (,ledger-comment-regex 2 + 'ledger-font-comment-face) + (,ledger-payee-pending-regex 2 + 'ledger-font-payee-pending-face) ; Works + (,ledger-payee-cleared-regex 2 + 'ledger-font-payee-cleared-face) ; Works + (,ledger-payee-uncleared-regex 2 + 'ledger-font-payee-uncleared-face) ; Works + (,ledger-account-cleared-regex 2 + 'ledger-font-posting-account-cleared-face) ; Works + (,ledger-account-pending-regex 2 + 'ledger-font-posting-account-pending-face) ; Works + (,ledger-account-any-status-regex 2 + 'ledger-font-posting-account-face) ; Works + (,ledger-other-entries-regex 1 + 'ledger-font-other-face)) + "Expressions to highlight in Ledger mode.") + + +(provide 'ldg-fonts) + +;;; ldg-fonts.el ends here diff --git a/lisp/ldg-init.el b/lisp/ldg-init.el new file mode 100644 index 00000000..f283c77c --- /dev/null +++ b/lisp/ldg-init.el @@ -0,0 +1,68 @@ +;;; ldg-init.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Determine the ledger environment + +(require 'ldg-regex) + +(defcustom ledger-init-file-name "~/.ledgerrc" + "Location of the ledger initialization file. nil if you don't have one" + :group 'ledger-exec) + +(defvar ledger-environment-alist nil) + +(defun ledger-init-parse-initialization (buffer) + (with-current-buffer buffer + (let (environment-alist) + (goto-char (point-min)) + (while (re-search-forward ledger-init-string-regex nil t ) + (let ((matchb (match-beginning 0)) ;; save the match data, string-match stamp on it + (matche (match-end 0))) + (end-of-line) + (setq environment-alist + (append environment-alist + (list (cons (let ((flag (buffer-substring-no-properties (+ 2 matchb) matche))) + (if (string-match "[ \t\n\r]+\\'" flag) + (replace-match "" t t flag) + flag)) + (let ((value (buffer-substring-no-properties matche (point) ))) + (if (> (length value) 0) + value + t)))))))) + environment-alist))) + +(defun ledger-init-load-init-file () + (interactive) + (let ((init-base-name (file-name-nondirectory ledger-init-file-name))) + (if (get-buffer init-base-name) ;; init file already loaded, parse it and leave it + (ledger-init-parse-initialization init-base-name) + (when (and ledger-init-file-name + (file-exists-p ledger-init-file-name) + (file-readable-p ledger-init-file-name)) + (find-file-noselect ledger-init-file-name) + (setq ledger-environment-alist + (ledger-init-parse-initialization init-base-name)) + (kill-buffer init-base-name))))) + +(provide 'ldg-init) + +;;; ldg-init.el ends here diff --git a/lisp/ldg-mode.el b/lisp/ldg-mode.el index 4d13d7d2..4bc195ed 100644 --- a/lisp/ldg-mode.el +++ b/lisp/ldg-mode.el @@ -1,118 +1,210 @@ -(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 - '(("\\( \\| \\|^\\)\\(;.*\\)" 2 font-lock-comment-face) - ("^[0-9]+[-/.=][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\(\\( ;\\| ;\\|$\\)\\)" 2 bold) - ;;("^[0-9]+[-/.=][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([*].+?\\)\\(\\( ;\\| ;\\|$\\)\\)" - ;; 2 font-lock-type-face) - ("^\\s-+\\([*]\\s-*\\)?\\(\\([[(]\\)?[^*: - ]+?:\\([^]); - ]\\|\\s-\\)+?\\([])]\\)?\\)\\( \\| \\|$\\)" - 2 font-lock-keyword-face) - ("^\\([~=].+\\)" 1 font-lock-function-name-face) - ("^\\([A-Za-z]+ .+\\)" 1 font-lock-function-name-face)) - "Expressions to highlight in Ledger mode.") +;;; ldg-mode.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Most of the general ledger-mode code is here. + +;;; Code: + +(defsubst ledger-current-year () + "The default current year for adding transactions." + (format-time-string "%Y")) +(defsubst ledger-current-month () + "The default current month for adding transactions." + (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-read-account-with-prompt (prompt) + (let* ((context (ledger-context-at-point)) + (default (if (and (eq (ledger-context-line-type context) 'acct-transaction) + (eq (ledger-context-current-field context) 'account)) + (regexp-quote (ledger-context-field-value context 'account)) + nil))) + (ledger-read-string-with-default prompt default))) + +(defun ledger-read-string-with-default (prompt default) + "Return user supplied string after PROMPT, or DEFAULT." + (read-string (concat prompt + (if default + (concat " (" default "): ") + ": ")) + nil 'ledger-minibuffer-history default)) + +(defun ledger-display-balance-at-point () + "Display the cleared-or-pending balance. +And calculate the target-delta of the account being reconciled." + (interactive) + (let* ((account (ledger-read-account-with-prompt "Account balance to show")) + (buffer (current-buffer)) + (balance (with-temp-buffer + (ledger-exec-ledger buffer (current-buffer) "cleared" account) + (buffer-substring-no-properties (point-min) (1- (point-max)))))) + (when balance + (message balance)))) + +(defun ledger-magic-tab (&optional interactively) + "Decide what to with with <TAB> . +Can be pcomplete, or align-posting" + (interactive "p") + (if (and (> (point) 1) + (looking-back "[:A-Za-z0-9]" 1)) + (ledger-pcomplete interactively) + (ledger-post-align-postings))) (defvar ledger-mode-abbrev-table) +(defun ledger-insert-effective-date () + (interactive) + (let ((context (car (ledger-context-at-point))) + (date-string (format-time-string (cdr (assoc "date-format" ledger-environment-alist))))) + (cond ((eq 'xact context) + (beginning-of-line) + (insert date-string "=")) + ((eq 'acct-transaction context) + (end-of-line) + (insert " ; [=" date-string "]"))))) + ;;;###autoload (define-derived-mode ledger-mode text-mode "Ledger" - "A mode for editing ledger data files." - (ledger-post-setup) - - (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 ?e)] 'ledger-toggle-current-entry) - (define-key map [(control ?c) (control ?r)] 'ledger-reconcile) - (define-key map [(control ?c) (control ?s)] 'ledger-sort) - (define-key map [(control ?c) (control ?t)] 'ledger-test-run) - (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))) - -(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 &optional insert-at-point) - (interactive "sEntry: ") - (let* ((args (with-temp-buffer - (insert entry-text) - (eshell-parse-arguments (point-min) (point-max)))) - (ledger-buf (current-buffer)) - exit-code) - (unless insert-at-point - (let ((date (car args))) - (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 - (insert - (with-temp-buffer - (setq exit-code - (apply #'ledger-run-ledger ledger-buf "entry" - (mapcar 'eval args))) - (goto-char (point-min)) - (if (looking-at "Error: ") - (error (buffer-string)) - (buffer-string))) - "\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)))) + "A mode for editing ledger data files." + (ledger-check-version) + (ledger-post-setup) + + (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) "") + + (add-hook 'post-command-hook 'ledger-highlight-xact-under-point nil t) + (add-hook 'before-revert-hook 'ledger-occur-remove-all-overlays nil t) + (make-variable-buffer-local 'highlight-overlay) + + (ledger-init-load-init-file) + + (set (make-local-variable 'indent-region-function) 'ledger-post-align-postings) + + (let ((map (current-local-map))) + (define-key map [(control ?c) (control ?a)] 'ledger-add-transaction) + (define-key map [(control ?c) (control ?b)] 'ledger-post-edit-amount) + (define-key map [(control ?c) (control ?c)] 'ledger-toggle-current) + (define-key map [(control ?c) (control ?d)] 'ledger-delete-current-transaction) + (define-key map [(control ?c) (control ?e)] 'ledger-toggle-current-transaction) + (define-key map [(control ?c) (control ?f)] 'ledger-occur) + (define-key map [(control ?c) (control ?k)] 'ledger-copy-transaction-at-point) + (define-key map [(control ?c) (control ?m)] 'ledger-set-month) + (define-key map [(control ?c) (control ?r)] 'ledger-reconcile) + (define-key map [(control ?c) (control ?s)] 'ledger-sort-region) + (define-key map [(control ?c) (control ?t)] 'ledger-insert-effective-date) + (define-key map [(control ?c) (control ?u)] 'ledger-schedule-upcoming) + (define-key map [(control ?c) (control ?y)] 'ledger-set-year) + (define-key map [(control ?c) (control ?p)] 'ledger-display-balance-at-point) + (define-key map [tab] 'ledger-magic-tab) + (define-key map [(control ?i)] 'ledger-magic-tab) + (define-key map [(control ?c) tab] 'ledger-fully-complete-xact) + (define-key map [(control ?c) (control ?i)] 'ledger-fully-complete-xact) + + (define-key map [(control ?c) (control ?o) (control ?a)] 'ledger-report-redo) + (define-key map [(control ?c) (control ?o) (control ?e)] 'ledger-report-edit) + (define-key map [(control ?c) (control ?o) (control ?g)] 'ledger-report-goto) + (define-key map [(control ?c) (control ?o) (control ?k)] 'ledger-report-kill) + (define-key map [(control ?c) (control ?o) (control ?r)] 'ledger-report) + (define-key map [(control ?c) (control ?o) (control ?s)] 'ledger-report-save) + + (define-key map [(meta ?p)] 'ledger-post-prev-xact) + (define-key map [(meta ?n)] 'ledger-post-next-xact) + + (define-key map [menu-bar] (make-sparse-keymap "ldg-menu")) + (define-key map [menu-bar ldg-menu] (cons "Ledger" map)) + + (define-key map [report-kill] '(menu-item "Kill Report" ledger-report-kill :enable ledger-works)) + (define-key map [report-edit] '(menu-item "Edit Report" ledger-report-edit :enable ledger-works)) + (define-key map [report-save] '(menu-item "Save Report" ledger-report-save :enable ledger-works)) + (define-key map [report-rrun] '(menu-item "Re-run Report" ledger-report-redo :enable ledger-works)) + (define-key map [report-goto] '(menu-item "Goto Report" ledger-report-goto :enable ledger-works)) + (define-key map [report-run] '(menu-item "Run Report" ledger-report :enable ledger-works)) + (define-key map [sep5] '(menu-item "--")) + (define-key map [set-month] '(menu-item "Set Month" ledger-set-month :enable ledger-works)) + (define-key map [set-year] '(menu-item "Set Year" ledger-set-year :enable ledger-works)) + (define-key map [cust] '(menu-item "Customize Ledger Mode" (lambda () + (interactive) + (customize-group 'ledger)))) + (define-key map [sep1] '("--")) + (define-key map [effective-date] '(menu-item "Set effective date" ledger-insert-effective-date)) + (define-key map [sort-end] '(menu-item "Mark Sort End" ledger-sort-insert-end-mark)) + (define-key map [sort-start] '(menu-item "Mark Sort Beginning" ledger-sort-insert-start-mark)) + (define-key map [sort-buff] '(menu-item "Sort Buffer" ledger-sort-buffer)) + (define-key map [sort-reg] '(menu-item "Sort Region" ledger-sort-region :enable mark-active)) + (define-key map [align-reg] '(menu-item "Align Region" ledger-post-align-postings :enable mark-active)) + (define-key map [sep2] '(menu-item "--")) + (define-key map [copy-xact] '(menu-item "Copy Trans at Point" ledger-copy-transaction-at-point)) + (define-key map [toggle-post] '(menu-item "Toggle Current Posting" ledger-toggle-current)) + (define-key map [toggle-xact] '(menu-item "Toggle Current Transaction" ledger-toggle-current-transaction)) + (define-key map [sep4] '(menu-item "--")) + (define-key map [recon-account] '(menu-item "Reconcile Account" ledger-reconcile)) + (define-key map [check-balance] '(menu-item "Check Balance" ledger-display-balance-at-point)) + (define-key map [sep6] '(menu-item "--")) + (define-key map [edit-amount] '(menu-item "Calc on Amount" ledger-post-edit-amount)) + (define-key map [sep] '(menu-item "--")) + (define-key map [delete-xact] '(menu-item "Delete Transaction" ledger-delete-current-transaction)) + (define-key map [cmp-xact] '(menu-item "Complete Transaction" ledger-fully-complete-xact)) + (define-key map [add-xact] '(menu-item "Add Transaction (ledger xact)" ledger-add-transaction :enable ledger-works)) + (define-key map [sep3] '(menu-item "--")) + (define-key map [reconcile] '(menu-item "Reconcile Account" ledger-reconcile :enable ledger-works)) + (define-key map [reconcile] '(menu-item "Narrow to REGEX" ledger-occur)))) + + + + +(defun ledger-set-year (newyear) + "Set ledger's idea of the current year to the prefix argument NEWYEAR." + (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 NEWMONTH." + (interactive "p") + (if (= newmonth 1) + (setq ledger-month (read-string "Month: " (ledger-current-month))) + (setq ledger-month (format "%02d" newmonth)))) + + (provide 'ldg-mode) + +;;; ldg-mode.el ends here diff --git a/lisp/ldg-new.el b/lisp/ldg-new.el index 64377bb9..bed99ac0 100644 --- a/lisp/ldg-new.el +++ b/lisp/ldg-new.el @@ -31,15 +31,29 @@ ;; MA 02111-1307, USA. ;;; Commentary: - -(require 'ldg-post) -(require 'ldg-mode) +;; Load up the ledger mode +(require 'ldg-regex) +(require 'esh-util) +(require 'esh-arg) +(require 'ldg-commodities) (require 'ldg-complete) +(require 'ldg-context) +(require 'ldg-exec) +(require 'ldg-fonts) +(require 'ldg-init) +(require 'ldg-mode) +(require 'ldg-occur) +(require 'ldg-post) +(require 'ldg-reconcile) +(require 'ldg-report) +(require 'ldg-sort) (require 'ldg-state) +(require 'ldg-test) +(require 'ldg-texi) +(require 'ldg-xact) +(require 'ldg-schedule) -;(autoload #'ledger-mode "ldg-mode" nil t) -;(autoload #'ledger-fully-complete-entry "ldg-complete" nil t) -;(autoload #'ledger-toggle-current "ldg-state" nil t) +;;; Code: (autoload #'ledger-texi-update-test "ldg-texi" nil t) (autoload #'ledger-texi-update-examples "ldg-texi" nil t) @@ -49,34 +63,28 @@ :group 'data) (defconst ledger-version "3.0" - "The version of ledger.el currently loaded") + "The version of ledger.el currently loaded.") -(provide 'ledger) +(defun ledger-mode-dump-variable (var) + (if var + (insert (format " %s: %S\n" (symbol-name var) (eval var))))) + +(defun ledger-mode-dump-group (group) + "Dump GROUP customizations to current buffer" + (let ((members (custom-group-members group nil))) + (dolist (member members) + (cond ((eq (cadr member) 'custom-group) + (insert (format "Group %s:\n" (symbol-name (car member)))) + (ledger-mode-dump-group (car member))) + ((eq (cadr member) 'custom-variable) + (ledger-mode-dump-variable (car member))))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun ledger-mode-dump-configuration () + "Dump all customizations" + (find-file "ledger-mode-dump") + (ledger-mode-dump-group 'ledger)) + +(provide 'ledger) -(defun ledger-create-test () - (interactive) - (save-restriction - (org-narrow-to-subtree) - (save-excursion - (let (text beg) - (goto-char (point-min)) - (forward-line 1) - (setq beg (point)) - (search-forward ":PROPERTIES:") - (goto-char (line-beginning-position)) - (setq text (buffer-substring-no-properties beg (point))) - (goto-char (point-min)) - (re-search-forward ":ID:\\s-+\\([^-]+\\)") - (find-file-other-window - (format "~/src/ledger/test/regress/%s.test" (match-string 1))) - (sit-for 0) - (insert text) - (goto-char (point-min)) - (while (not (eobp)) - (goto-char (line-beginning-position)) - (delete-char 3) - (forward-line 1)))))) +;;; ldg-new.el ends here -;;; ledger.el ends here diff --git a/lisp/ldg-occur.el b/lisp/ldg-occur.el new file mode 100644 index 00000000..96c364d6 --- /dev/null +++ b/lisp/ldg-occur.el @@ -0,0 +1,210 @@ +;;; ldg-mode.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Provide buffer narrowing to ledger mode. Adapted from original loccur +;; mode by Alexey Veretennikov <alexey dot veretennikov at gmail dot +;; com> +;; +;; Adapted to ledger mode by Craig Earls <enderww at gmail dot +;; com> + +;;; Code: + +(defconst ledger-occur-overlay-property-name 'ledger-occur-custom-buffer-grep) + +(defcustom ledger-occur-use-face-shown t + "If non-nil, use a custom face for xacts shown in `ledger-occur' mode using ledger-occur-xact-face." + :type 'boolean + :group 'ledger) +(make-variable-buffer-local 'ledger-occur-use-face-shown) + + +(defvar ledger-occur-mode nil +"name of the minor mode, shown in the mode-line") + +(make-variable-buffer-local 'ledger-occur-mode) + +(or (assq 'ledger-occur-mode minor-mode-alist) + (nconc minor-mode-alist + (list '(ledger-occur-mode ledger-occur-mode)))) + +(defvar ledger-occur-history nil + "History of previously searched expressions for the prompt.") +;;(make-variable-buffer-local 'ledger-occur-history) + +(defvar ledger-occur-last-match nil + "Last match found.") +(make-variable-buffer-local 'ledger-occur-last-match) + +(defvar ledger-occur-overlay-list nil + "A list of currently active overlays to the ledger buffer.") +(make-variable-buffer-local 'ledger-occur-overlay-list) + +(defun ledger-remove-all-overlays () + "Remove all overlays from the ledger buffer." + (interactive) + (remove-overlays)) + +(defun ledger-occur-mode (regex buffer) + "Highlight transactions that match REGEX in BUFFER, hiding others. + +When REGEX is nil, unhide everything, and remove higlight" + (set-buffer buffer) + (setq ledger-occur-mode + (if (or (null regex) + (zerop (length regex))) + nil + (concat " Ledger-Narrowed: " regex))) + (force-mode-line-update) + (ledger-occur-remove-overlays) + (if ledger-occur-mode + (let* ((buffer-matches (ledger-occur-find-matches regex)) + (ovl-bounds (ledger-occur-create-xact-overlay-bounds buffer-matches))) + (setq ledger-occur-overlay-list + (append (ledger-occur-create-xact-overlays ovl-bounds) + (ledger-occur-create-narrowed-overlays buffer-matches))) + (setq ledger-occur-last-match regex) + (if (get-buffer-window buffer) + (select-window (get-buffer-window buffer))))) + (recenter)) + +(defun ledger-occur (regex) + "Perform a simple grep in current buffer for the regular expression REGEX. + + This command hides all xact from the current buffer except + those containing the regular expression REGEX. A second call + of the function unhides lines again" + (interactive + (if ledger-occur-mode + (list nil) + (list (read-string (concat "Regexp<" (ledger-occur-prompt) ">: ") + nil 'ledger-occur-history (ledger-occur-prompt))))) + (ledger-occur-mode regex (current-buffer))) + +(defun ledger-occur-prompt () + "Return the default value of the prompt. + + Default value for prompt is a current word or active + region(selection), if its size is 1 line" + (let ((prompt + (if (and transient-mark-mode + mark-active) + (let ((pos1 (region-beginning)) + (pos2 (region-end))) + ;; Check if the start and the of an active region is on + ;; the same line + (if (= (line-number-at-pos pos1) + (line-number-at-pos pos2)) + (buffer-substring-no-properties pos1 pos2))) + (current-word)))) + prompt)) + +(defun ledger-occur-create-narrowed-overlays(buffer-matches) + (if buffer-matches + (let ((overlays + (let ((prev-end (point-min))) + (mapcar (lambda (match) + (prog1 + (make-overlay prev-end (car match) + (current-buffer) t nil) + (setq prev-end (1+ (cadr match))))) + buffer-matches)))) + (mapcar (lambda (ovl) + (overlay-put ovl ledger-occur-overlay-property-name t) + (overlay-put ovl 'invisible t) + (overlay-put ovl 'intangible t)) + (push (make-overlay (cadr (car(last buffer-matches))) + (point-max) + (current-buffer) t nil) overlays))))) + + +(defun ledger-occur-create-xact-overlays (ovl-bounds) + "Create the overlay for the visible transactions. +Argument OVL-BOUNDS contains bounds for the transactions to be left visible." + (let ((overlays + (mapcar (lambda (bnd) + (make-overlay (car bnd) + (cadr bnd) + (current-buffer) t nil)) + ovl-bounds))) + (mapcar (lambda (ovl) + (overlay-put ovl ledger-occur-overlay-property-name t) + (if ledger-occur-use-face-shown + (overlay-put ovl 'face 'ledger-occur-xact-face ))) + overlays))) + +(defun ledger-occur-quit-buffer (buffer) + "Quits hidings transaction in the given BUFFER. +Used for coordinating `ledger-occur' with other buffers, like reconcile." + (set-buffer buffer) + (setq ledger-occur-mode nil) + (force-mode-line-update) + (ledger-occur-remove-overlays) + (recenter)) + +(defun ledger-occur-remove-overlays () + "Remove the transaction hiding overlays." + (interactive) + (remove-overlays (point-min) + (point-max) ledger-occur-overlay-property-name t) + (setq ledger-occur-overlay-list nil)) + + +(defun ledger-occur-create-xact-overlay-bounds (buffer-matches) + "Use BUFFER-MATCHES to produce the overlay for the visible transactions." + (let ((prev-end (point-min)) + (overlays (list))) + (when buffer-matches + (mapc (lambda (line) + (push (list (car line) (cadr line)) overlays) + (setq prev-end (cadr line))) + buffer-matches) + (setq overlays (nreverse overlays))))) + + +(defun ledger-occur-find-matches (regex) + "Return a list of 2-number tuples describing the beginning and start of transactions meeting REGEX." + (save-excursion + (goto-char (point-min)) + ;; Set initial values for variables + (let (curpoint + endpoint + (lines (list))) + ;; Search loop + (while (not (eobp)) + (setq curpoint (point)) + ;; if something found + (when (setq endpoint (re-search-forward regex nil 'end)) + (save-excursion + (let ((bounds (ledger-find-xact-extents (match-beginning 0)))) + (push bounds lines) + (setq curpoint (cadr bounds)))) ;; move to the end of + ;; the xact, no need to + ;; search inside it more + (goto-char curpoint)) + (forward-line 1)) + (setq lines (nreverse lines))))) + + +(provide 'ldg-occur) + +;;; ldg-occur.el ends here diff --git a/lisp/ldg-post.el b/lisp/ldg-post.el index 05b9d352..37722fbc 100644 --- a/lisp/ldg-post.el +++ b/lisp/ldg-post.el @@ -1,30 +1,61 @@ +;;; ldg-post.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Utility functions for dealing with postings. + (require 'ldg-regex) +;;; Code: + (defgroup ledger-post nil - "" + "Options for controlling how Ledger-mode deals with postings and completion" :group 'ledger) -(defcustom ledger-post-auto-adjust-amounts nil - "If non-nil, ." +(defcustom ledger-post-auto-adjust-postings t + "If non-nil, adjust account and amount to columns set below" :type 'boolean :group 'ledger-post) -(defcustom ledger-post-amount-alignment-column 52 - "If non-nil, ." - :type 'integer +(defcustom ledger-post-account-alignment-column 4 + "The column Ledger-mode attempts to align accounts to." + :type 'integer :group 'ledger-post) -(defcustom ledger-post-use-iswitchb nil - "If non-nil, ." - :type 'boolean +(defcustom ledger-post-amount-alignment-column 52 + "The column Ledger-mode attempts to align amounts to." + :type 'integer :group 'ledger-post) -(defcustom ledger-post-use-ido nil - "If non-nil, ." - :type 'boolean - :group 'ledger-post) +(defcustom ledger-post-use-completion-engine :built-in + "Which completion engine to use, :iswitchb or :ido chose those engines, +:built-in uses built-in Ledger-mode completion" + :type '(radio (const :tag "built in completion" :built-in) + (const :tag "ido completion" :ido) + (const :tag "iswitchb completion" :iswitchb) ) + :group 'ledger-post) (defun ledger-post-all-accounts () + "Return a list of all accounts in the buffer." (let ((origin (point)) (ledger-post-list nil) account elements) @@ -38,27 +69,28 @@ (declare-function iswitchb-read-buffer "iswitchb" (prompt &optional default require-match start matches-set)) + (defvar iswitchb-temp-buflist) (defun ledger-post-completing-read (prompt choices) - "Use iswitchb as a completing-read replacement to choose from choices. + "Use iswitchb as a `completing-read' replacement to choose from choices. PROMPT is a string to prompt with. CHOICES is a list of strings to choose from." - (cond - (ledger-post-use-iswitchb - (let* ((iswitchb-use-virtual-buffers nil) - (iswitchb-make-buflist-hook - (lambda () - (setq iswitchb-temp-buflist choices)))) - (iswitchb-read-buffer prompt))) - (ledger-post-use-ido - (ido-completing-read prompt choices)) - (t - (completing-read prompt choices)))) + (cond ((eq ledger-post-use-completion-engine :iswitchb) + (let* ((iswitchb-use-virtual-buffers nil) + (iswitchb-make-buflist-hook + (lambda () + (setq iswitchb-temp-buflist choices)))) + (iswitchb-read-buffer prompt))) + ((eq ledger-post-use-completion-engine :ido) + (ido-completing-read prompt choices)) + (t + (completing-read prompt choices)))) (defvar ledger-post-current-list nil) (defun ledger-post-pick-account () + "Insert an account entered by the user." (interactive) (let* ((account (ledger-post-completing-read @@ -75,78 +107,129 @@ to choose from." (match-end ledger-regex-post-line-group-account)) (insert account) (cond - ((> existing-len account-len) - (insert (make-string (- existing-len account-len) ? ))) - ((< existing-len account-len) - (dotimes (n (- account-len existing-len)) - (if (looking-at "[ \t]\\( [ \t]\\|\t\\)") - (delete-char 1))))))) + ((> existing-len account-len) + (insert (make-string (- existing-len account-len) ? ))) + ((< existing-len account-len) + (dotimes (n (- account-len existing-len)) + (if (looking-at "[ \t]\\( [ \t]\\|\t\\)") + (delete-char 1))))))) (goto-char pos))) -(defun ledger-next-amount (&optional end) - (when (re-search-forward "\\( \\|\t\\| \t\\)[ \t]*-?\\([A-Z$]+ *\\)?\\(-?[0-9,]+?\\)\\(.[0-9]+\\)?\\( *[A-Z$]+\\)?\\([ \t]*@@?[^\n;]+?\\)?\\([ \t]+;.+?\\)?$" (marker-position end) t) + + +(defsubst ledger-next-amount (&optional end) + "Move point to the next amount, as long as it is not past END. +Return the width of the amount field as an integer and leave +point at beginning of the commodity." + ;;(beginning-of-line) + (when (re-search-forward ledger-amount-regex end t) (goto-char (match-beginning 0)) (skip-syntax-forward " ") (- (or (match-end 4) (match-end 3)) (point)))) -(defun ledger-align-amounts (&optional column) - "Align amounts in the current region. -This is done so that the last digit falls in COLUMN, which defaults to 52." - (interactive "p") - (if (or (null column) (= column 1)) - (setq column ledger-post-amount-alignment-column)) - (save-excursion - (let* ((mark-first (< (mark) (point))) - (begin (if mark-first (mark) (point))) - (end (if mark-first (point-marker) (mark-marker))) - offset) - (goto-char begin) - (while (setq offset (ledger-next-amount end)) - (let ((col (current-column)) - (target-col (- column offset)) - adjust) - (setq adjust (- target-col col)) - (if (< col target-col) - (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)))))) - -(defun ledger-post-align-amount () + +(defun ledger-next-account (&optional end) + "Move point to the beginning of the next account, or status marker (!*), as long as it is not past END. +Return the column of the beginning of the account and leave point +at beginning of account" + (if (> end (point)) + (when (re-search-forward ledger-account-any-status-regex (1+ end) t) + ;; the 1+ is to make sure we can catch the newline + (if (match-beginning 1) + (goto-char (match-beginning 1)) + (goto-char (match-beginning 2))) + (current-column)))) + +(defun ledger-post-align-postings (&optional beg end) + "Align all accounts and amounts within region, if there is no +region align the posting on the current line." (interactive) + (assert (eq major-mode 'ledger-mode)) + (save-excursion - (set-mark (line-beginning-position)) - (goto-char (1+ (line-end-position))) - (ledger-align-amounts))) + (if (or (not (mark)) + (not (use-region-p))) + (set-mark (point))) + + (let* ((inhibit-modification-hooks t) + (mark-first (< (mark) (point))) + (begin-region (if beg + beg + (if mark-first (mark) (point)))) + (end-region (if end + end + (if mark-first (point) (mark)))) + acct-start-column acct-end-column acct-adjust amt-width + (lines-left 1)) + ;; Condition point and mark to the beginning and end of lines + (goto-char end-region) + (setq end-region (line-end-position)) + (goto-char begin-region) + (goto-char + (setq begin-region + (line-beginning-position))) + + ;; This is the guts of the alignment loop + (while (and (or (setq acct-start-column (ledger-next-account (line-end-position))) + lines-left) + (< (point) end-region)) + (when acct-start-column + (setq acct-end-column (save-excursion + (goto-char (match-end 2)) + (current-column))) + (when (/= (setq acct-adjust (- ledger-post-account-alignment-column acct-start-column)) 0) + (setq acct-end-column (+ acct-end-column acct-adjust)) ;;adjust the account ending column + (if (> acct-adjust 0) + (insert (make-string acct-adjust ? )) + (delete-char acct-adjust))) + (when (setq amt-width (ledger-next-amount (line-end-position))) + (if (/= 0 (setq amt-adjust (- (if (> (- ledger-post-amount-alignment-column amt-width) + (+ 2 acct-end-column)) + ledger-post-amount-alignment-column ;;we have room + (+ acct-end-column 2 amt-width)) + amt-width + (current-column)))) + (if (> amt-adjust 0) + (insert (make-string amt-adjust ? )) + (delete-char amt-adjust))))) + (forward-line) + (setq lines-left (not (eobp)))) + (setq inhibit-modification-hooks nil)))) (defun ledger-post-maybe-align (beg end len) - (save-excursion - (goto-char beg) - (when (< end (line-end-position)) - (goto-char (line-beginning-position)) - (if (looking-at ledger-post-line-regexp) - (ledger-post-align-amount))))) + "Align amounts only if point is in a posting. +BEG, END, and LEN control how far it can align." + (if ledger-post-auto-adjust-postings + (save-excursion + (goto-char beg) + (when (<= end (line-end-position)) + (goto-char (line-beginning-position)) + (if (looking-at ledger-post-line-regexp) + (ledger-post-align-postings)))))) (defun ledger-post-edit-amount () + "Call 'calc-mode' and push the amount in the posting to the top of stack." (interactive) (goto-char (line-beginning-position)) (when (re-search-forward ledger-post-line-regexp (line-end-position) t) - (goto-char (match-end ledger-regex-post-line-group-account)) - (when (re-search-forward "[-.,0-9]+" (line-end-position) t) - (let ((val (match-string 0))) - (goto-char (match-beginning 0)) - (delete-region (match-beginning 0) (match-end 0)) - (calc) - (while (string-match "," val) - (setq val (replace-match "" nil nil val))) - (calc-eval val 'push))))) + (goto-char (match-end ledger-regex-post-line-group-account)) ;; go to the and of the account + (let ((end-of-amount (re-search-forward "[-.,0-9]+" (line-end-position) t))) + ;; determine if there is an amount to edit + (if end-of-amount + (let ((val (ledger-commodity-string-number-decimalize (match-string 0) :from-user))) + (goto-char (match-beginning 0)) + (delete-region (match-beginning 0) (match-end 0)) + (calc) + (calc-eval val 'push)) ;; edit the amount + (progn ;;make sure there are two spaces after the account name and go to calc + (if (search-backward " " (- (point) 3) t) + (goto-char (line-end-position)) + (insert " ")) + (calc)))))) (defun ledger-post-prev-xact () + "Move point to the previous transaction." (interactive) (backward-paragraph) (when (re-search-backward ledger-xact-line-regexp nil t) @@ -155,6 +238,7 @@ This is done so that the last digit falls in COLUMN, which defaults to 52." (goto-char (match-end ledger-regex-post-line-group-account)))) (defun ledger-post-next-xact () + "Move point to the next transaction." (interactive) (when (re-search-forward ledger-xact-line-regexp nil t) (goto-char (match-beginning 0)) @@ -162,13 +246,14 @@ This is done so that the last digit falls in COLUMN, which defaults to 52." (goto-char (match-end ledger-regex-post-line-group-account)))) (defun ledger-post-setup () - (let ((map (current-local-map))) - (define-key map [(meta ?p)] 'ledger-post-prev-xact) - (define-key map [(meta ?n)] 'ledger-post-next-xact) - (define-key map [(control ?c) (control ?c)] 'ledger-post-pick-account) - (define-key map [(control ?c) (control ?e)] 'ledger-post-edit-amount)) - (if ledger-post-auto-adjust-amounts - (add-hook 'after-change-functions 'ledger-post-maybe-align t t)) - (add-hook 'after-save-hook #'(lambda () (setq ledger-post-current-list nil)))) + "Configure `ledger-mode' to auto-align postings." + (add-hook 'after-change-functions 'ledger-post-maybe-align t t) + (add-hook 'after-save-hook #'(lambda () (setq ledger-post-current-list nil)) t t)) + + (provide 'ldg-post) + + + +;;; ldg-post.el ends here diff --git a/lisp/ldg-reconcile.el b/lisp/ldg-reconcile.el index baeadc33..ca4d0004 100644 --- a/lisp/ldg-reconcile.el +++ b/lisp/ldg-reconcile.el @@ -1,141 +1,462 @@ +;;; ldg-reconcile.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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. + ;; Reconcile mode + +;;; Commentary: +;; Code to handle reconciling Ledger files wiht outside sources + +;;; Code: + (defvar ledger-buf nil) +(defvar ledger-bufs nil) (defvar ledger-acct nil) +(defvar ledger-target nil) + +(defgroup ledger-reconcile nil + "Options for Ledger-mode reconciliation" + :group 'ledger) + +(defcustom ledger-recon-buffer-name "*Reconcile*" + "Name to use for reconciliation window." + :group 'ledger-reconcile) + +(defcustom ledger-narrow-on-reconcile t + "If t, limit transactions shown in main buffer to those matching the reconcile regex." + :type 'boolean + :group 'ledger-reconcile) + +(defcustom ledger-buffer-tracks-reconcile-buffer t + "If t, then when the cursor is moved to a new xact in the recon window. +Then that transaction will be shown in its source buffer." + :type 'boolean + :group 'ledger-reconcile) + +(defcustom ledger-reconcile-force-window-bottom nil + "If t make the reconcile window appear along the bottom of the register window and resize." + :type 'boolean + :group 'ledger-reconcile) + +(defcustom ledger-reconcile-toggle-to-pending t + "If true then toggle between uncleared and pending. +reconcile-finish will mark all pending posting cleared." + :type 'boolean + :group 'ledger-reconcile) + +(defcustom ledger-reconcile-default-date-format "%Y/%m/%d" + "Default date format for the reconcile buffer" + :type 'string + :group 'ledger-reconcile) + +(defcustom ledger-reconcile-target-prompt-string "Target amount for reconciliation " + "Default prompt for recon target prompt" + :type 'string + :group 'ledger-reconcile) + + +(defun ledger-reconcile-get-cleared-or-pending-balance (buffer account) + "Calculate the cleared or pending balance of the account." + + ;; these vars are buffer local, need to hold them for use in the + ;; temp buffer below + + (with-temp-buffer + ;; note that in the line below, the --format option is + ;; separated from the actual format string. emacs does not + ;; split arguments like the shell does, so you need to + ;; specify the individual fields in the command line. + (if (ledger-exec-ledger buffer (current-buffer) + "balance" "--limit" "cleared or pending" "--empty" "--collapse" + "--format" "%(display_total)" account) + (ledger-split-commodity-string + (buffer-substring-no-properties (point-min) (point-max)))))) (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)))))))) + "Display the cleared-or-pending balance. +And calculate the target-delta of the account being reconciled." + (interactive) + (let* ((pending (ledger-reconcile-get-cleared-or-pending-balance ledger-buf ledger-acct))) + (when pending + (if ledger-target + (message "Pending balance: %s, Difference from target: %s" + (ledger-commodity-to-string pending) + (ledger-commodity-to-string (-commodity ledger-target pending))) + (message "Pending balance: %s" + (ledger-commodity-to-string pending)))))) + +(defun is-stdin (file) + "True if ledger FILE is standard input." + (or + (equal file "") + (equal file "<stdin>") + (equal file "/dev/stdin"))) + +(defun ledger-reconcile-get-buffer (where) + "Return a buffer from WHERE the transaction is." + (if (bufferp (car where)) + (car where) + (error "Function ledger-reconcile-get-buffer: Buffer not set"))) (defun ledger-reconcile-toggle () + "Toggle the current transaction, and mark the recon window." (interactive) + (beginning-of-line) (let ((where (get-text-property (point) 'where)) - (account ledger-acct) (inhibit-read-only t) - cleared) - (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin")) - (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))) + status) + (when (ledger-reconcile-get-buffer where) + (with-current-buffer (ledger-reconcile-get-buffer where) + (ledger-goto-line (cdr where)) + (forward-char) + (setq status (ledger-toggle-current (if ledger-reconcile-toggle-to-pending + 'pending + 'cleared)))) + ;; remove the existing face and add the new face + (remove-text-properties (line-beginning-position) + (line-end-position) + (list 'face)) + (cond ((eq status 'pending) + (add-text-properties (line-beginning-position) + (line-end-position) + (list 'face 'ledger-font-reconciler-pending-face ))) + ((eq status 'cleared) + (add-text-properties (line-beginning-position) + (line-end-position) + (list 'face 'ledger-font-reconciler-cleared-face ))) + (t + (add-text-properties (line-beginning-position) + (line-end-position) + (list 'face 'ledger-font-reconciler-uncleared-face ))))) + (forward-line) + (beginning-of-line) + (ledger-display-balance))) (defun ledger-reconcile-refresh () + "Force the reconciliation window to refresh. +Return the number of uncleared xacts found." (interactive) - (let ((inhibit-read-only t) - (line (count-lines (point-min) (point)))) + (let ((inhibit-read-only t)) (erase-buffer) - (ledger-do-reconcile) - (set-buffer-modified-p t) - (goto-char (point-min)) - (forward-line line))) + (prog1 + (ledger-do-reconcile) + (set-buffer-modified-p t)))) (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))))) + "Refresh the recon-window after the ledger buffer is saved." + (let ((curbuf (current-buffer)) + (curpoint (point)) + (recon-buf (get-buffer ledger-recon-buffer-name))) + (when (buffer-live-p recon-buf) + (with-current-buffer recon-buf + (ledger-reconcile-refresh) + (set-buffer-modified-p nil)) + (select-window (get-buffer-window curbuf)) + (goto-char curpoint)))) (defun ledger-reconcile-add () + "Use ledger xact to add a new transaction." (interactive) (with-current-buffer ledger-buf - (call-interactively #'ledger-add-entry)) + (call-interactively #'ledger-add-transaction)) (ledger-reconcile-refresh)) (defun ledger-reconcile-delete () + "Delete the transactions pointed to in the recon window." (interactive) (let ((where (get-text-property (point) 'where))) - (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin")) - (with-current-buffer ledger-buf - (goto-char (cdr where)) - (ledger-delete-current-entry)) + (when (ledger-reconcile-get-buffer where) + (with-current-buffer (ledger-reconcile-get-buffer where) + (ledger-goto-line (cdr where)) + (ledger-delete-current-transaction)) (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 () +(defun ledger-reconcile-visit (&optional come-back) + "Recenter ledger buffer on transaction and COME-BACK if non-nil." (interactive) - (let ((where (get-text-property (point) 'where))) - (when (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin")) - (switch-to-buffer-other-window ledger-buf) - (goto-char (cdr where))))) + (progn + (beginning-of-line) + (let* ((where (get-text-property (1+ (point)) 'where)) + (target-buffer (if where + (ledger-reconcile-get-buffer where) + nil)) + (cur-buf (get-buffer ledger-recon-buffer-name))) + (when target-buffer + (switch-to-buffer-other-window target-buffer) + (ledger-goto-line (cdr where)) + (forward-char) + (recenter) + (ledger-highlight-xact-under-point) + (forward-char -1) + (if come-back + (switch-to-buffer-other-window cur-buf)))))) (defun ledger-reconcile-save () + "Save the ledger buffer." (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))) + (let ((curpoint (point))) + (dolist (buf (cons ledger-buf ledger-bufs)) + (with-current-buffer buf + (save-buffer))) + (with-current-buffer (get-buffer ledger-recon-buffer-name) + (set-buffer-modified-p nil) + (ledger-display-balance) + (goto-char curpoint) + (ledger-reconcile-visit t)))) (defun ledger-reconcile-finish () + "Mark all pending posting or transactions as cleared. +Depends on ledger-reconcile-clear-whole-transactions, save the buffers +and exit reconcile mode" (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) - (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin"))) - (with-current-buffer ledger-buf - (goto-char (cdr where)) + (if (eq face 'ledger-font-reconciler-pending-face) + (with-current-buffer (ledger-reconcile-get-buffer where) + (ledger-goto-line (cdr where)) (ledger-toggle-current 'cleared)))) (forward-line 1))) (ledger-reconcile-save)) + +(defun ledger-reconcile-quit () + "Quit the reconcile window without saving ledger buffer." + (interactive) + (let ((recon-buf (get-buffer ledger-recon-buffer-name)) + buf) + (if recon-buf + (with-current-buffer recon-buf + (ledger-reconcile-quit-cleanup) + (setq buf ledger-buf) + ;; Make sure you delete the window before you delete the buffer, + ;; otherwise, madness ensues + (delete-window (get-buffer-window recon-buf)) + (kill-buffer recon-buf) + (set-window-buffer (selected-window) buf))))) + +(defun ledger-reconcile-quit-cleanup () + "Cleanup all hooks established by reconcile mode." + (interactive) + (let ((buf ledger-buf)) + (if (buffer-live-p buf) + (with-current-buffer buf + (remove-hook 'after-save-hook 'ledger-reconcile-refresh-after-save t) + (when ledger-narrow-on-reconcile + (ledger-occur-quit-buffer buf) + (ledger-highlight-xact-under-point)))))) + +(defun ledger-marker-where-xact-is (emacs-xact posting) + "Find the position of the EMACS-XACT in the `ledger-buf'. +POSTING is used in `ledger-clear-whole-transactions' is nil." + (let ((buf (if (is-stdin (nth 0 emacs-xact)) + ledger-buf + (find-file-noselect (nth 0 emacs-xact))))) + (cons + buf + (if ledger-clear-whole-transactions + (nth 1 emacs-xact) ;; return line-no of xact + (nth 0 posting))))) ;; return line-no of posting + (defun ledger-do-reconcile () - ) - -(defun ledger-reconcile (account) - (interactive "sAccount to reconcile: ") - (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)))) + "Return the number of uncleared transactions in the account and display them in the *Reconcile* buffer." + (let* ((buf ledger-buf) + (account ledger-acct) + (ledger-success nil) + (xacts + (with-temp-buffer + (when (ledger-exec-ledger buf (current-buffer) + "--uncleared" "--real" "emacs" account) + (setq ledger-success t) + (goto-char (point-min)) + (unless (eobp) + (if (looking-at "(") + (read (current-buffer)))))))) ;current-buffer is the *temp* created above + (if (and ledger-success (> (length xacts) 0)) + (let ((date-format (cdr (assoc "date-format" ledger-environment-alist)))) + (dolist (xact xacts) + (dolist (posting (nthcdr 5 xact)) + (let ((beg (point)) + (where (ledger-marker-where-xact-is xact posting))) + (insert (format "%s %-4s %-30s %-30s %15s\n" + (format-time-string (if date-format + date-format + ledger-reconcile-default-date-format) (nth 2 xact)) + (if (nth 3 xact) + (nth 3 xact) + "") + (nth 4 xact) (nth 1 posting) (nth 2 posting))) + (if (nth 3 posting) + (if (eq (nth 3 posting) 'pending) + (set-text-properties beg (1- (point)) + (list 'face 'ledger-font-reconciler-pending-face + 'where where)) + (set-text-properties beg (1- (point)) + (list 'face 'ledger-font-reconciler-cleared-face + 'where where))) + (set-text-properties beg (1- (point)) + (list 'face 'ledger-font-reconciler-uncleared-face + 'where where)))) )) + (goto-char (point-max)) + (delete-char -1)) ;gets rid of the extra line feed at the bottom of the list + (if ledger-success + (insert (concat "There are no uncleared entries for " account)) + (insert "Ledger has reported a problem. Check *Ledger Error* buffer."))) + (goto-char (point-min)) + (set-buffer-modified-p nil) + (toggle-read-only t) + + (ledger-reconcile-ensure-xacts-visible) + (length xacts))) + +(defun ledger-reconcile-ensure-xacts-visible () + "Ensures that the last of the visible transactions in the +ledger buffer is at the bottom of the main window. The key to +this is to ensure the window is selected when the buffer point is +moved and recentered. If they aren't strange things happen." + + (let ((recon-window (get-buffer-window (get-buffer ledger-recon-buffer-name)))) + (when recon-window + (fit-window-to-buffer recon-window) + (with-current-buffer buf + (add-hook 'kill-buffer-hook 'ledger-reconcile-quit nil t) + (if (get-buffer-window buf) + (select-window (get-buffer-window buf))) + (goto-char (point-max)) + (recenter -1)) + (select-window recon-window) + (ledger-reconcile-visit t)) + (add-hook 'post-command-hook 'ledger-reconcile-track-xact nil t))) + +(defun ledger-reconcile-track-xact () + "Force the ledger buffer to recenter on the transaction at point in the reconcile buffer." + (if (and ledger-buffer-tracks-reconcile-buffer + (member this-command (list 'next-line + 'previous-line + 'mouse-set-point + 'ledger-reconcile-toggle + 'end-of-buffer + 'beginning-of-buffer))) + (save-excursion + (ledger-reconcile-visit t)))) + +(defun ledger-reconcile-open-windows (buf rbuf) + "Ensure that the ledger buffer BUF is split by RBUF." + (if ledger-reconcile-force-window-bottom + ;;create the *Reconcile* window directly below the ledger buffer. + (set-window-buffer (split-window (get-buffer-window buf) nil nil) rbuf) + (pop-to-buffer rbuf))) + +(defun ledger-reconcile () + "Start reconciling, prompt for account." + (interactive) + (let ((account (ledger-read-account-with-prompt "Account to reconcile")) + (buf (current-buffer)) + (rbuf (get-buffer ledger-recon-buffer-name))) + ;; this means only one *Reconcile* buffer, ever Set up the + ;; reconcile buffer + (if rbuf ;; *Reconcile* already exists + (with-current-buffer rbuf + (set 'ledger-acct account) ;; already buffer local + (when (not (eq buf rbuf)) + ;; called from some other ledger-mode buffer + (ledger-reconcile-quit-cleanup) + (set 'ledger-buf buf)) ;; should already be buffer-local + + (unless (get-buffer-window rbuf) + (ledger-reconcile-open-windows buf rbuf))) + + ;; no recon-buffer, starting from scratch. + (add-hook 'after-save-hook 'ledger-reconcile-refresh-after-save nil t) + + (with-current-buffer (setq rbuf + (get-buffer-create ledger-recon-buffer-name)) + (ledger-reconcile-open-windows buf rbuf) + (ledger-reconcile-mode) + (make-local-variable 'ledger-target) + (set (make-local-variable 'ledger-buf) buf) + (set (make-local-variable 'ledger-acct) account))) + + ;; Narrow the ledger buffer + (with-current-buffer rbuf + (save-excursion + (if ledger-narrow-on-reconcile + (ledger-occur-mode account ledger-buf))) + (if (> (ledger-reconcile-refresh) 0) + (ledger-reconcile-change-target)) + (ledger-display-balance)))) (defvar ledger-reconcile-mode-abbrev-table) +(defun ledger-reconcile-change-target () + "Change the target amount for the reconciliation process." + (interactive) + (setq ledger-target (ledger-read-commodity-string ledger-reconcile-target-prompt-string))) + (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 ?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 [?s] 'ledger-reconcile-save) - (define-key map [?q] 'ledger-reconcile-quit) - (use-local-map map))) + "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 ?l)] 'ledger-reconcile-refresh) + (define-key map [(control ?c) (control ?c)] 'ledger-reconcile-finish) + (define-key map [? ] 'ledger-reconcile-toggle) + (define-key map [?a] 'ledger-reconcile-add) + (define-key map [?d] 'ledger-reconcile-delete) + (define-key map [?g] 'ledger-reconcile); + (define-key map [?n] 'next-line) + (define-key map [?p] 'previous-line) + (define-key map [?t] 'ledger-reconcile-change-target) + (define-key map [?s] 'ledger-reconcile-save) + (define-key map [?q] 'ledger-reconcile-quit) + (define-key map [?b] 'ledger-display-balance) + + (define-key map [menu-bar] (make-sparse-keymap "ldg-recon-menu")) + (define-key map [menu-bar ldg-recon-menu] (cons "Reconcile" map)) + (define-key map [menu-bar ldg-recon-menu qui] '("Quit" . ledger-reconcile-quit)) + (define-key map [menu-bar ldg-recon-menu sep1] '("--")) + (define-key map [menu-bar ldg-recon-menu pre] '("Previous Entry" . previous-line)) + (define-key map [menu-bar ldg-recon-menu vis] '("Visit Source" . ledger-reconcile-visit)) + (define-key map [menu-bar ldg-recon-menu nex] '("Next Entry" . next-line)) + (define-key map [menu-bar ldg-recon-menu sep2] '("--")) + (define-key map [menu-bar ldg-recon-menu del] '("Delete Entry" . ledger-reconcile-delete)) + (define-key map [menu-bar ldg-recon-menu add] '("Add Entry" . ledger-reconcile-add)) + (define-key map [menu-bar ldg-recon-menu tog] '("Toggle Entry" . ledger-reconcile-toggle)) + (define-key map [menu-bar ldg-recon-menu sep3] '("--")) + (define-key map [menu-bar ldg-recon-menu bal] '("Show Cleared Balance" . ledger-display-balance)) + (define-key map [menu-bar ldg-recon-menu tgt] '("Change Target Balance" . ledger-reconcile-change-target)) + (define-key map [menu-bar ldg-recon-menu sep4] '("--")) + (define-key map [menu-bar ldg-recon-menu rna] '("Reconcile New Account" . ledger-reconcile)) + (define-key map [menu-bar ldg-recon-menu sep5] '("--")) + (define-key map [menu-bar ldg-recon-menu fin] '("Finish" . ledger-reconcile-finish)) + (define-key map [menu-bar ldg-recon-menu ref] '("Refresh" . ledger-reconcile-refresh)) + (define-key map [menu-bar ldg-recon-menu sav] '("Save" . ledger-reconcile-save)) + + (use-local-map map))) + +(provide 'ldg-reconcile) + +;;; ldg-reconcile.el ends here diff --git a/lisp/ldg-regex.el b/lisp/ldg-regex.el index 1c6b8f06..226475df 100644 --- a/lisp/ldg-regex.el +++ b/lisp/ldg-regex.el @@ -1,15 +1,83 @@ +;;; ldg-regex.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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. + (require 'rx) (eval-when-compile (require 'cl)) +(defconst ledger-amount-regex + (concat "\\( \\|\t\\| \t\\)[ \t]*-?" + "\\([A-Z$€£_]+ *\\)?" + "\\(-?[0-9,]+?\\)" + "\\(.[0-9]+\\)?" + "\\( *[[:word:]€£_\"]+\\)?" + "\\([ \t]*[@={]@?[^\n;]+?\\)?" + "\\([ \t]+;.+?\\|[ \t]*\\)?$")) + +(defconst ledger-amount-decimal-comma-regex + "-?[1-9][0-9.]*[,]?[0-9]*") + +(defconst ledger-amount-decimal-period-regex + "-?[1-9][0-9.]*[.]?[0-9]*") + +(defconst ledger-other-entries-regex + "\\(^[~=A-Za-z].+\\)+") + +(defconst ledger-comment-regex + "\\( \\| \\|^\\)\\(;.*\\)") + +(defconst ledger-payee-any-status-regex + "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") + +(defconst ledger-payee-pending-regex + "^[0-9]+[-/][-/.=0-9]+\\s-\\!\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\(;\\|$\\)") + +(defconst ledger-payee-cleared-regex + "^[0-9]+[-/][-/.=0-9]+\\s-\\*\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\(;\\|$\\)") + +(defconst ledger-payee-uncleared-regex + "^[0-9]+[-/][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\(;\\|$\\)") + +(defconst ledger-init-string-regex + "^--.+?\\($\\|[ ]\\)") + +(defconst ledger-account-any-status-regex + "^[ \t]+\\([*!]\\s-+\\)?\\([[(]?.+?\\)\\(\t\\|\n\\| [ \t]\\)") + +(defconst ledger-account-pending-regex + "\\(^[ \t]+\\)\\(!.+?\\)\\( \\|$\\)") + +(defconst ledger-account-cleared-regex + "\\(^[ \t]+\\)\\(\\*.+?\\)\\( \\|$\\)") + + + (defmacro ledger-define-regexp (name regex docs &rest args) "Simplify the creation of a Ledger regex and helper functions." (let ((defs - (list - `(defconst - ,(intern (concat "ledger-" (symbol-name name) "-regexp")) - ,(eval regex)))) + (list + `(defconst + ,(intern (concat "ledger-" (symbol-name name) "-regexp")) + ,(eval regex)))) (addend 0) last-group) (if (null args) (progn @@ -17,104 +85,104 @@ defs (list `(defconst - ,(intern - (concat "ledger-regex-" (symbol-name name) "-group")) + ,(intern + (concat "ledger-regex-" (symbol-name name) "-group")) 1))) (nconc defs (list `(defconst - ,(intern (concat "ledger-regex-" (symbol-name name) - "-group--count")) + ,(intern (concat "ledger-regex-" (symbol-name name) + "-group--count")) 1))) (nconc defs (list `(defmacro - ,(intern (concat "ledger-regex-" (symbol-name name))) - (&optional string) + ,(intern (concat "ledger-regex-" (symbol-name name))) + (&optional string) ,(format "Return the match string for the %s" name) (match-string ,(intern (concat "ledger-regex-" (symbol-name name) "-group")) string))))) - - (dolist (arg args) - (let (var grouping target) - (if (symbolp arg) - (setq var arg target arg) - (assert (listp arg)) - (if (= 2 (length arg)) - (setq var (car arg) - target (cadr arg)) - (setq var (car arg) - grouping (cadr arg) - target (caddr arg)))) - - (if (and last-group - (not (eq last-group (or grouping target)))) - (incf addend - (symbol-value - (intern-soft (concat "ledger-regex-" - (symbol-name last-group) - "-group--count"))))) - (nconc - defs - (list - `(defconst - ,(intern (concat "ledger-regex-" (symbol-name name) - "-group-" (symbol-name var))) - ,(+ addend - (symbol-value - (intern-soft - (if grouping - (concat "ledger-regex-" (symbol-name grouping) - "-group-" (symbol-name target)) - (concat "ledger-regex-" (symbol-name target) - "-group")))))))) - (nconc - defs - (list - `(defmacro - ,(intern (concat "ledger-regex-" (symbol-name name) - "-" (symbol-name var))) - (&optional string) - ,(format "Return the sub-group match for the %s %s." - name var) - (match-string - ,(intern (concat "ledger-regex-" (symbol-name name) - "-group-" (symbol-name var))) - string)))) + + (dolist (arg args) + (let (var grouping target) + (if (symbolp arg) + (setq var arg target arg) + (assert (listp arg)) + (if (= 2 (length arg)) + (setq var (car arg) + target (cadr arg)) + (setq var (car arg) + grouping (cadr arg) + target (caddr arg)))) + + (if (and last-group + (not (eq last-group (or grouping target)))) + (incf addend + (symbol-value + (intern-soft (concat "ledger-regex-" + (symbol-name last-group) + "-group--count"))))) + (nconc + defs + (list + `(defconst + ,(intern (concat "ledger-regex-" (symbol-name name) + "-group-" (symbol-name var))) + ,(+ addend + (symbol-value + (intern-soft + (if grouping + (concat "ledger-regex-" (symbol-name grouping) + "-group-" (symbol-name target)) + (concat "ledger-regex-" (symbol-name target) + "-group")))))))) + (nconc + defs + (list + `(defmacro + ,(intern (concat "ledger-regex-" (symbol-name name) + "-" (symbol-name var))) + (&optional string) + ,(format "Return the sub-group match for the %s %s." + name var) + (match-string + ,(intern (concat "ledger-regex-" (symbol-name name) + "-group-" (symbol-name var))) + string)))) - (setq last-group (or grouping target)))) + (setq last-group (or grouping target)))) - (nconc defs - (list - `(defconst ,(intern (concat "ledger-regex-" (symbol-name name) - "-group--count")) - ,(length args))))) + (nconc defs + (list + `(defconst ,(intern (concat "ledger-regex-" (symbol-name name) + "-group--count")) + ,(length args))))) (cons 'progn defs))) (put 'ledger-define-regexp 'lisp-indent-function 1) -(ledger-define-regexp date - (let ((sep '(or ?- (any ?. ?/)))) ; can't do (any ?- ?. ?/) due to bug +(ledger-define-regexp iso-date + ( let ((sep '(or ?- ?/))) (rx (group - (and (? (= 4 num) - (eval sep)) - (and num (? num)) + (and (group (? (= 4 num))) + (eval sep) + (group (and num (? num))) (eval sep) - (and num (? num)))))) + (group (and num (? num))))))) "Match a single date, in its 'written' form.") (ledger-define-regexp full-date (macroexpand - `(rx (and (regexp ,ledger-date-regexp) - (? (and ?= (regexp ,ledger-date-regexp)))))) + `(rx (and (regexp ,ledger-iso-date-regexp) + (? (and ?= (regexp ,ledger-iso-date-regexp)))))) "Match a compound date, of the form ACTUAL=EFFECTIVE" - (actual date) - (effective date)) + (actual iso-date) + (effective iso-date)) (ledger-define-regexp state (rx (group (any ?! ?*))) @@ -211,7 +279,7 @@ (macroexpand `(rx (* (+ blank) (or (and ?\{ (regexp ,ledger-commoditized-amount-regexp) ?\}) - (and ?\[ (regexp ,ledger-date-regexp) ?\]) + (and ?\[ (regexp ,ledger-iso-date-regexp) ?\]) (and ?\( (not (any ?\))) ?\)))))) "") @@ -247,4 +315,12 @@ (amount full-amount) (note end-note)) +(defconst ledger-iterate-regex + (concat "\\(Y\\s-+\\([0-9]+\\)\\|" ;; Catches a Y directive + ledger-iso-date-regexp + "\\([ *!]+\\)" ;; mark + "\\((.*)\\)" ;; code + "\\(.*\\)" ;; desc + "\\)")) + (provide 'ldg-regex) diff --git a/lisp/ldg-register.el b/lisp/ldg-register.el deleted file mode 100644 index 7b5c0d0a..00000000 --- a/lisp/ldg-register.el +++ /dev/null @@ -1,66 +0,0 @@ -(require 'ldg-post) -(require 'ldg-state) - -(defgroup ledger-register nil - "" - :group 'ledger) - -(defcustom ledger-register-date-format "%m/%d/%y" - "*The date format used for ledger register reports." - :type 'string - :group 'ledger-register) - -(defcustom ledger-register-line-format "%s %-30.30s %-25.25s %15s\n" - "*The date format used for ledger register reports." - :type 'string - :group 'ledger-register) - -(defface ledger-register-pending-face - '((((background light)) (:weight bold)) - (((background dark)) (:weight bold))) - "Face used to highlight pending entries in a register report." - :group 'ledger-register) - -(defun ledger-register-render (data-buffer posts) - (dolist (post posts) - (let ((index 1)) - (dolist (xact (nthcdr 5 post)) - (let ((beg (point)) - (where - (with-current-buffer data-buffer - (cons - (nth 0 post) - (if ledger-clear-whole-entries - (save-excursion - (goto-line (nth 1 post)) - (point-marker)) - (save-excursion - (goto-line (nth 0 xact)) - (point-marker))))))) - (insert (format ledger-register-line-format - (format-time-string ledger-register-date-format - (nth 2 post)) - (nth 4 post) (nth 1 xact) (nth 2 xact))) - (if (nth 3 xact) - (set-text-properties beg (1- (point)) - (list 'face 'ledger-register-pending-face - 'where where)) - (set-text-properties beg (1- (point)) - (list 'where where)))) - (setq index (1+ index))))) - (goto-char (point-min)) - ) - -(defun ledger-register-generate (&optional data-buffer &rest args) - (let ((buf (or data-buffer (current-buffer)))) - (with-current-buffer (get-buffer-create "*ledger-register*") - (let ((pos (point)) - (inhibit-read-only t)) - (erase-buffer) - (ledger-register-render buf (apply #'ledger-exec-read buf args)) - (goto-char pos)) - (set-buffer-modified-p nil) - (toggle-read-only t) - (display-buffer (current-buffer) t)))) - -(provide 'ldg-register) diff --git a/lisp/ldg-report.el b/lisp/ldg-report.el index 5a668847..c3b83f55 100644 --- a/lisp/ldg-report.el +++ b/lisp/ldg-report.el @@ -1,7 +1,41 @@ +;;; ldg-report.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Provide facilities for running and saving reports in emacs + +;;; Code: + +(eval-when-compile + (require 'cl)) + +(defgroup ledger-report nil + "Customization option for the Report buffer" + :group 'ledger) + (defcustom ledger-reports '(("bal" "ledger -f %(ledger-file) bal") ("reg" "ledger -f %(ledger-file) reg") - ("payee" "ledger -f %(ledger-file) reg -- %(payee)") + ("payee" "ledger -f %(ledger-file) reg @%(payee)") ("account" "ledger -f %(ledger-file) reg %(account)")) "Definition of reports to run. @@ -10,33 +44,25 @@ contain format specifiers that are replaced with context sensitive information. Format specifiers have the format '%(<name>)' where <name> 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 +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) + (string :tag "Command Line"))) + :group 'ledger-report) (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 + ("account" . ledger-report-account-format-specifier) + ("value" . ledger-report-value-format-specifier)) + "An 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) - -;;(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) - -;; Ledger report mode + :group 'ledger-report) (defvar ledger-report-buffer-name "*Ledger Report*") @@ -45,28 +71,64 @@ text that should replace the format specifier." (defvar ledger-report-name-prompt-history nil) (defvar ledger-report-cmd-prompt-history nil) (defvar ledger-original-window-cfg nil) - +(defvar ledger-report-saved nil) +(defvar ledger-minibuffer-history nil) (defvar ledger-report-mode-abbrev-table) +(defun ledger-report-reverse-lines () + (interactive) + (goto-char (point-min)) + (forward-paragraph) + (forward-line) + (save-excursion + (setq inhibit-read-only t) + (reverse-region (point) (point-max)))) + (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 [backspace] 'scroll-down) - (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))) + "A mode for viewing ledger reports." + (let ((map (make-sparse-keymap))) + (define-key map [? ] 'scroll-up) + (define-key map [backspace] 'scroll-down) + (define-key map [?r] 'ledger-report-redo) + (define-key map [(shift ?r)] 'ledger-report-reverse-lines) + (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) + (define-key map [return] 'ledger-report-visit-source) + + + (define-key map [menu-bar] (make-sparse-keymap "ldg-rep")) + (define-key map [menu-bar ldg-rep] (cons "Reports" map)) + + (define-key map [menu-bar ldg-rep lrq] '("Quit" . ledger-report-quit)) + (define-key map [menu-bar ldg-rep s2] '("--")) + (define-key map [menu-bar ldg-rep lrd] '("Scroll Down" . scroll-down)) + (define-key map [menu-bar ldg-rep vis] '("Visit Source" . ledger-report-visit-source)) + (define-key map [menu-bar ldg-rep lru] '("Scroll Up" . scroll-up)) + (define-key map [menu-bar ldg-rep s1] '("--")) + (define-key map [menu-bar ldg-rep rev] '("Reverse report order" . ledger-report-reverse-lines)) + (define-key map [menu-bar ldg-rep s0] '("--")) + (define-key map [menu-bar ldg-rep lrk] '("Kill Report" . ledger-report-kill)) + (define-key map [menu-bar ldg-rep lrr] '("Re-run Report" . ledger-report-redo)) + (define-key map [menu-bar ldg-rep lre] '("Edit Report" . ledger-report-edit)) + (define-key map [menu-bar ldg-rep lrs] '("Save Report" . ledger-report-save)) + + (use-local-map map))) + +(defun ledger-report-value-format-specifier () + "Return a valid meta-data tag name" + ;; It is intended completion should be available on existing account + ;; names, but it remains to be implemented. + (ledger-read-string-with-default "Value: " nil)) (defun ledger-report-read-name () "Read the name of a ledger report to use, with completion. @@ -79,13 +141,14 @@ The empty string and unknown names are allowed." (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. +Prompts the user for the REPORT-NAME of the report to run or +EDIT. 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 @@ -106,6 +169,7 @@ used to generate the buffer, navigating the buffer, etc." (with-current-buffer (pop-to-buffer (get-buffer-create ledger-report-buffer-name)) (ledger-report-mode) + (set (make-local-variable 'ledger-report-saved) nil) (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) @@ -116,18 +180,19 @@ used to generate the buffer, navigating the buffer, etc." (message "q to quit; r to redo; e to edit; k to kill; s to save; SPC and DEL to scroll")))) (defun string-empty-p (s) - "Check for the empty string." + "Check S for the empty string." (string-equal "" s)) (defun ledger-report-name-exists (name) - "Check to see if the given report name exists. + "Check to see if the given report NAME exists. -If name exists, returns the object naming the report, otherwise returns nil." + 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'." + "Add a new report NAME and CMD to `ledger-reports'." (setq ledger-reports (cons (list name cmd) ledger-reports))) (defun ledger-reports-custom-save () @@ -135,17 +200,18 @@ If name exists, returns the object naming the report, otherwise returns nil." (customize-save-variable 'ledger-reports ledger-reports)) (defun ledger-report-read-command (report-cmd) - "Read the command line to create a report." + "Read the command line to create a report from REPORT-CMD." (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 + "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." + The master file name is determined by the variable `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)) ;; General helper functions @@ -155,86 +221,128 @@ otherwise the current buffer file is used." (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." + 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))) - -(defun ledger-read-string-with-default (prompt default) - (let ((default-prompt (concat prompt - (if default - (concat " (" default "): ") - ": ")))) - (read-string default-prompt nil nil default))) + (buffer-file-name))) (defun ledger-report-payee-format-specifier () - "Substitute a payee name + "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 + The user is prompted to enter a payee and that is substitued. If + point is in an xact, the payee for that xact is used as the + default." + ;; It is intended completion 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)))) + (ledger-read-string-with-default "Payee" (regexp-quote (ledger-xact-payee)))) (defun ledger-report-account-format-specifier () - "Substitute an account name + "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." + The user is prompted to enter an account name, which can be any + regular expression identifying an account. If point is on an account + posting line for an xact, 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))) + (ledger-read-account-with-prompt "Account")) (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)) + "Expand %(account) and %(payee) appearing in REPORT-CMD with thing under point." + (save-match-data + (let ((expanded-cmd report-cmd)) + (set-match-data (list 0 0)) + (while (string-match "%(\\([^)]*\\))" expanded-cmd (if (> (length expanded-cmd) (match-end 0)) + (match-end 0) + (1- (length 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))))) + expanded-cmd))) (defun ledger-report-cmd (report-name edit) - "Get the command line to run the report." + "Get the command line to run the report name REPORT-NAME. +Optional EDIT the command." (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-read-command report-cmd)) + (setq ledger-report-saved nil)) ;; this is a new report, or edited report (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)) + (progn + (ledger-reports-add report-name report-cmd) + (ledger-reports-custom-save))) report-cmd)) (defun ledger-do-report (cmd) - "Run a report command line." + "Run a report command line CMD." (goto-char (point-min)) (insert (format "Report: %s\n" ledger-report-name) (format "Command: %s\n" cmd) (make-string (- (window-width) 1) ?=) - "\n") - (shell-command cmd t nil)) + "\n\n") + (let ((data-pos (point)) + (register-report (string-match " reg\\(ister\\)? " cmd)) + files-in-report) + (shell-command + ;; --subtotal does not produce identifiable transactions, so don't + ;; prepend location information for them + (if (and register-report + (not (string-match "--subtotal" cmd))) + (concat cmd " --prepend-format='%(filename):%(beg_line):'") + cmd) + t nil) + (when register-report + (goto-char data-pos) + (while (re-search-forward "^\\(/[^:]+\\)?:\\([0-9]+\\)?:" nil t) + (let ((file (match-string 1)) + (line (string-to-number (match-string 2)))) + (delete-region (match-beginning 0) (match-end 0)) + (when file + (set-text-properties (line-beginning-position) (line-end-position) + (list 'ledger-source (cons file (save-window-excursion + (save-excursion + (find-file file) + (widen) + (ledger-goto-line line) + (point-marker)))))) + (add-text-properties (line-beginning-position) (line-end-position) + (list 'face 'ledger-font-report-clickable-face)) + (end-of-line))))) + (goto-char data-pos))) + + +(defun ledger-report-visit-source () + "Visit the transaction under point in the report window." + (interactive) + (let* ((prop (get-text-property (point) 'ledger-source)) + (file (if prop (car prop))) + (line-or-marker (if prop (cdr prop)))) + (when (and file line-or-marker) + (find-file-other-window file) + (widen) + (if (markerp line-or-marker) + (goto-char line-or-marker) + (goto-char (point-min)) + (forward-line (1- line-or-marker)) + (re-search-backward "^[0-9]+") + (beginning-of-line) + (let ((start-of-txn (point))) + (forward-paragraph) + (narrow-to-region start-of-txn (point)) + (backward-paragraph)))))) (defun ledger-report-goto () "Goto the ledger report buffer." @@ -288,161 +396,24 @@ the default." (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))) - -(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)))) + (if (setq existing-name (ledger-report-name-exists ledger-report-name)) + (cond ((y-or-n-p (format "Overwrite existing report named '%s'? " + ledger-report-name)) + (if (string-equal + ledger-report-cmd + (car (cdr (assq existing-name ledger-reports)))) + (message "Nothing to save. Current command is identical to existing saved one") + (progn + (setq ledger-reports + (assq-delete-all existing-name ledger-reports)) + (ledger-reports-add ledger-report-name ledger-report-cmd) + (ledger-reports-custom-save)))) + (t + (progn + (setq ledger-report-name (ledger-report-read-new-name)) + (ledger-reports-add ledger-report-name ledger-report-cmd) + (ledger-reports-custom-save))))))) + +(provide 'ldg-report) + +;;; ldg-report.el ends here diff --git a/lisp/ldg-schedule.el b/lisp/ldg-schedule.el new file mode 100644 index 00000000..885c0876 --- /dev/null +++ b/lisp/ldg-schedule.el @@ -0,0 +1,330 @@ +;;; ldg-schedule.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2013 Craig Earls (enderw88 at gmail dot com) + +;; 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: +;; +;; This module provides for automatically adding transactions to a +;; ledger buffer on a periodic basis. Recurrence expressions are +;; inspired by Martin Fowler's "Recurring Events for Calendars", +;; martinfowler.com/apsupp/recurring.pdf + +;; use (fset 'VARNAME (macro args)) to put the macro definition in the +;; function slot of the symbol VARNAME. Then use VARNAME as the +;; function without have to use funcall. + +(defgroup ledger-schedule nil + "Support for automatically recommendation transactions." + :group 'ledger) + +(defcustom ledger-schedule-buffer-name "*Ledger Schedule*" + "Name for the schedule buffer" + :type 'string + :group 'ledger-schedule) + +(defcustom ledger-schedule-look-backward 7 + "Number of days to look back in time for transactions." + :type 'integer + :group 'ledger-schedule) + +(defcustom ledger-schedule-look-forward 14 + "Number of days auto look forward to recommend transactions" + :type 'integer + :group 'ledger-schedule) + +(defcustom ledger-schedule-file "~/FinanceData/ledger-schedule.ledger" + "File to find scheduled transactions." + :type 'file + :group 'ledger-schedule) + +(defsubst between (val low high) + (and (>= val low) (<= val high))) + +(defun ledger-schedule-days-in-month (month year) + "Return number of days in the MONTH, MONTH is from 1 to 12. +If year is nil, assume it is not a leap year" + (if (between month 1 12) + (if (and year (date-leap-year-p year) (= 2 month)) + 29 + (nth (1- month) '(31 28 31 30 31 30 31 31 30 31 30 31))) + (error "Month out of range, MONTH=%S" month))) + +;; Macros to handle date expressions + +(defun ledger-schedule-constrain-day-in-month (count day-of-week) + "Return a form that evaluates DATE that returns true for the COUNT DAY-OF-WEEK. +For example, return true if date is the 3rd Thursday of the +month. Negative COUNT starts from the end of the month. (EQ +COUNT 0) means EVERY day-of-week (eg. every Saturday)" + (if (and (between count -6 6) (between day-of-week 0 6)) + (cond ((zerop count) ;; Return true if day-of-week matches + `(eq (nth 6 (decode-time date)) ,day-of-week)) + ((> count 0) ;; Positive count + (let ((decoded (gensym))) + `(let ((,decoded (decode-time date))) + (and (eq (nth 6 ,decoded) ,day-of-week) + (between (nth 3 ,decoded) + ,(* (1- count) 7) + ,(* count 7)))))) + ((< count 0) + (let ((days-in-month (gensym)) + (decoded (gensym))) + `(let* ((,decoded (decode-time date)) + (,days-in-month (ledger-schedule-days-in-month + (nth 4 ,decoded) + (nth 5 ,decoded)))) + (and (eq (nth 6 ,decoded) ,day-of-week) + (between (nth 3 ,decoded) + (+ ,days-in-month ,(* count 7)) + (+ ,days-in-month ,(* (1+ count) 7))))))) + (t + (error "COUNT out of range, COUNT=%S" count))) + (error "Invalid argument to ledger-schedule-day-in-month-macro %S %S" + count + day-of-week))) + +(defun ledger-schedule-constrain-every-count-day (day-of-week skip start-date) + "Return a form that is true for every DAY skipping SKIP, starting on START. +For example every second Friday, regardless of month." + (let ((start-day (nth 6 (decode-time (eval start-date))))) + (if (eq start-day day-of-week) ;; good, can proceed + `(zerop (mod (- (time-to-days date) ,(time-to-days (eval start-date))) ,(* skip 7))) + (error "START-DATE day of week doesn't match DAY-OF-WEEK")))) + +(defun ledger-schedule-constrain-date-range (month1 day1 month2 day2) + "Return a form of DATE that is true if DATE falls between MONTH1 DAY1 and MONTH2 DAY2." + (let ((decoded (gensym)) + (target-month (gensym)) + (target-day (gensym))) + `(let* ((,decoded (decode-time date)) + (,target-month (nth 4 decoded)) + (,target-day (nth 3 decoded))) + (and (and (> ,target-month ,month1) + (< ,target-month ,month2)) + (and (> ,target-day ,day1) + (< ,target-day ,day2)))))) + + +(defun ledger-schedule-is-holiday (date) + "Return true if DATE is a holiday.") + +(defun ledger-schedule-scan-transactions (schedule-file) + "Scans AUTO_FILE and returns a list of transactions with date predicates. +The car of each item is a fuction of date that returns true if +the transaction should be logged for that day." + (interactive "fFile name: ") + (let ((xact-list (list))) + (with-current-buffer + (find-file-noselect schedule-file) + (goto-char (point-min)) + (while (re-search-forward "^\\[\\(.*\\)\\] " nil t) + (let ((date-descriptor "") + (transaction nil) + (xact-start (match-end 0))) + (setq date-descriptors + (ledger-schedule-read-descriptor-tree + (buffer-substring-no-properties + (match-beginning 0) + (match-end 0)))) + (forward-paragraph) + (setq transaction (list date-descriptors + (buffer-substring-no-properties + xact-start + (point)))) + (setq xact-list (cons transaction xact-list)))) + xact-list))) + +(defun ledger-schedule-replace-brackets () + "Replace all brackets with parens" + (goto-char (point-min)) + (while (search-forward "]" nil t) + (replace-match ")" nil t)) + (goto-char (point-min)) + (while (search-forward "[" nil t) + (replace-match "(" nil t))) + +(defvar ledger-schedule-descriptor-regex + (concat "\\(20[0-9][0-9]\\|[\*]\\)[/\\-]" ;; Year slot + "\\([\*EO]\\|[01][0-9]\\)[/\\-]" ;; Month slot + "\\([\*]\\|\\([0-3][0-9]\\)\\|" + "\\([0-5]" + "\\(\\(Su\\)\\|" + "\\(Mo\\)\\|" + "\\(Tu\\)\\|" + "\\(We\\)\\|" + "\\(Th\\)\\|" + "\\(Fr\\)\\|" + "\\(Sa\\)\\)\\)\\)")) + +(defun ledger-schedule-read-descriptor-tree (descriptor-string) + "Take a date DESCRIPTOR-STRING and return a function of date that +returns true if the date meets the requirements" + (with-temp-buffer + ;; copy the descriptor string into a temp buffer for manipulation + (let (pos) + ;; Replace brackets with parens + (insert descriptor-string) + (ledger-schedule-replace-brackets) + + (goto-char (point-max)) + ;; double quote all the descriptors for string processing later + (while (re-search-backward ledger-schedule-descriptor-regex nil t) ;; Day slot + (goto-char + (match-end 0)) + (insert ?\") + (goto-char (match-beginning 0)) + (insert "\"" ))) + + ;; read the descriptor string into a lisp object the transform the + ;; string descriptor into useable things + (ledger-schedule-transform-auto-tree + (read (buffer-substring-no-properties (point-min) (point-max)))))) + +(defun ledger-schedule-transform-auto-tree (descriptor-string-list) +"Takes a lisp list of date descriptor strings, TREE, and returns a string with a lambda function of date." +;; use funcall to use the lambda function spit out here + (if (consp descriptor-string-list) + (let (result) + (while (consp descriptor-string-list) + (let ((newcar (car descriptor-string-list))) + (if (consp newcar) + (setq newcar (ledger-schedule-transform-auto-tree (car descriptor-string-list)))) + ;; newcar may be a cons now, after ledger-schedule-transfrom-auto-tree + (if (consp newcar) + (push newcar result) + ;; this is where we actually turn the string descriptor into useful lisp + (push (ledger-schedule-compile-constraints newcar) result)) ) + (setq descriptor-string-list (cdr descriptor-string-list))) + + ;; tie up all the clauses in a big or and lambda, and return + ;; the lambda function as list to be executed by funcall + `(lambda (date) + ,(nconc (list 'or) (nreverse result) descriptor-string-list))))) + +(defun ledger-schedule-compile-constraints (descriptor-string) + "Return a list with the year, month and day fields split" + (let ((fields (split-string descriptor-string "[/\\-]" t)) + constrain-year constrain-month constrain-day) + (setq constrain-year (ledger-schedule-constrain-year (nth 0 fields))) + (setq constrain-month (ledger-schedule-constrain-month (nth 1 fields))) + (setq constrain-day (ledger-schedule-constrain-day (nth 2 fields))) + + (list 'and constrain-year constrain-month constrain-day))) + +(defun ledger-schedule-constrain-year (str) + (let ((year-match t)) + (cond ((string= str "*") + year-match) + ((/= 0 (setq year-match (string-to-number str))) + `(eq (nth 5 (decode-time date)) ,year-match)) + (t + (error "Improperly specified year constraint: " str))))) + +(defun ledger-schedule-constrain-month (str) + + (let ((month-match t)) + (cond ((string= str "*") + month-match) ;; always match + ((/= 0 (setq month-match (string-to-number str))) + (if (between month-match 1 12) ;; no month specified, assume 31 days. + `(eq (nth 4 (decode-time date)) ,month-match) + (error "ledger-schedule-constrain-numerical-month: month out of range %S" month-match))) + (t + (error "Improperly specified month constraint: " str))))) + +(defun ledger-schedule-constrain-day (str) + (let ((day-match t)) + (cond ((string= str "*") + t) + ((/= 0 (setq day-match (string-to-number str))) + `(eq (nth 3 (decode-time date)) ,day-match)) + (t + (error "Improperly specified day constraint: " str))))) + +(defun ledger-schedule-parse-date-descriptor (descriptor) + "Parse the date descriptor, return the evaluator" + (ledger-schedule-compile-constraints descriptor)) + +(defun ledger-schedule-list-upcoming-xacts (candidate-items early horizon) + "Search CANDIDATE-ITEMS for xacts that occur within the period today - EARLY to today + HORIZON" + (let ((start-date (time-subtract (current-time) (days-to-time early))) + test-date items) + (loop for day from 0 to (+ early horizon) by 1 do + (setq test-date (time-add start-date (days-to-time day))) + (dolist (candidate candidate-items items) + (if (funcall (car candidate) test-date) + (setq items (append items (list (list test-date (cadr candidate)))))))) + items)) + +(defun ledger-schedule-already-entered (candidate buffer) + (let ((target-date (format-time-string date-format (car candidate))) + (target-payee (cadr candidate))) + nil)) + +(defun ledger-schedule-create-auto-buffer (candidate-items early horizon ledger-buf) + "Format CANDIDATE-ITEMS for display." + (let ((candidates (ledger-schedule-list-upcoming-xacts candidate-items early horizon)) + (schedule-buf (get-buffer-create ledger-schedule-buffer-name)) + (date-format (cdr (assoc "date-format" ledger-environment-alist)))) + (with-current-buffer schedule-buf + (erase-buffer) + (dolist (candidate candidates) + (if (not (ledger-schedule-already-entered candidate ledger-buf)) + (insert (format-time-string date-format (car candidate) ) " " (cadr candidate) "\n"))) + (ledger-mode)) + (length candidates))) + + +;; +;; Test harnesses for use in ielm +;; +(defvar auto-items) + +(defun ledger-schedule-test ( early horizon) + (ledger-schedule-create-auto-buffer + (ledger-schedule-scan-transactions ledger-schedule-file) + early + horizon + (get-buffer "2013.ledger"))) + + +(defun ledger-schedule-test-predict () + (let ((today (current-time)) + test-date items) + + (loop for day from 0 to ledger-schedule-look-forward by 1 do + (setq test-date (time-add today (days-to-time day))) + (dolist (item auto-items items) + (if (funcall (car item) test-date) + (setq items (append items (list (decode-time test-date) (cdr item))))))) + items)) + +(defun ledger-schedule-upcoming () + (interactive) + (ledger-schedule-create-auto-buffer + (ledger-schedule-scan-transactions ledger-schedule-file) + ledger-schedule-look-backward + ledger-schedule-look-forward + (current-buffer))) + + +(provide 'ldg-schedule) + +;;; ldg-schedule.el ends here diff --git a/lisp/ldg-sort.el b/lisp/ldg-sort.el new file mode 100644 index 00000000..a50cd1cc --- /dev/null +++ b/lisp/ldg-sort.el @@ -0,0 +1,113 @@ +;;; ldg-xact.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; + +;;; Code: + +(defun ledger-next-record-function () + "Move point to next transaction." + (if (re-search-forward ledger-payee-any-status-regex nil t) + (goto-char (match-beginning 0)) + (goto-char (point-max)))) + +(defun ledger-end-record-function () + "Move point to end of transaction." + (forward-paragraph)) + +(defun ledger-sort-find-start () + (if (re-search-forward ";.*Ledger-mode:.*Start sort" nil t) + (match-end 0))) + +(defun ledger-sort-find-end () + (if (re-search-forward ";.*Ledger-mode:.*End sort" nil t) + (match-end 0))) + +(defun ledger-sort-insert-start-mark () + (interactive) + (save-excursion + (goto-char (point-min)) + (if (ledger-sort-find-start) + (delete-region (match-beginning 0) (match-end 0)))) + (beginning-of-line) + (insert "\n; Ledger-mode: Start sort\n\n")) + +(defun ledger-sort-insert-end-mark () + (interactive) + (save-excursion + (goto-char (point-min)) + (if (ledger-sort-find-end) + (delete-region (match-beginning 0) (match-end 0)))) + (beginning-of-line) + (insert "\n; Ledger-mode: End sort\n\n")) + +(defun ledger-sort-startkey () + "Return the actual date so the sort-subr doesn't sort onthe entire first line." + (buffer-substring-no-properties (point) (+ 10 (point)))) + +(defun ledger-sort-region (beg end) + "Sort the region from BEG to END in chronological order." + (interactive "r") ;; load beg and end from point and mark + ;; automagically + (let ((new-beg beg) + (new-end end)) + (setq inhibit-modification-hooks t) + (save-excursion + (save-restriction + (goto-char beg) + (ledger-next-record-function) ;; make sure point is at the + ;; beginning of a xact + (setq new-beg (point)) + (goto-char end) + (ledger-next-record-function) ;; make sure end of region is at + ;; the beginning of next record + ;; after the region + (setq new-end (point)) + (narrow-to-region new-beg new-end) + (goto-char new-beg) + + (let ((inhibit-field-text-motion t)) + (sort-subr + nil + 'ledger-next-record-function + 'ledger-end-record-function + 'ledger-sort-startkey)))) + (setq inhibit-modification-hooks nil))) + +(defun ledger-sort-buffer () + "Sort the entire buffer." + (interactive) + (goto-char (point-min)) + (let ((sort-start (ledger-sort-find-start)) + (sort-end (ledger-sort-find-end))) + (ledger-sort-region (if sort-start + sort-start + (point-min)) + (if sort-end + sort-end + (point-max))))) + +(provide 'ldg-sort) + +;;; ldg-sort.el ends here diff --git a/lisp/ldg-state.el b/lisp/ldg-state.el index 6a841621..58777631 100644 --- a/lisp/ldg-state.el +++ b/lisp/ldg-state.el @@ -1,56 +1,94 @@ -(defcustom ledger-clear-whole-entries nil - "If non-nil, clear whole entries, not individual transactions." +;;; ldg-state.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Utilities for dealing with transaction and posting status. + +;;; Code: + +(defcustom ledger-clear-whole-transactions nil + "If non-nil, clear whole transactions, not individual postings." :type 'boolean :group 'ledger) -(defun ledger-toggle-state (state &optional style) - (if (not (null state)) - (if (and style (eq style 'cleared)) - 'cleared) - (if (and style (eq style 'pending)) - 'pending - 'cleared))) - -(defun ledger-entry-state () +(defun ledger-transaction-state () + "Return the state of the transaction at point." (save-excursion (when (or (looking-at "^[0-9]") (re-search-backward "^[0-9]" nil t)) - (skip-chars-forward "0-9./=") + (skip-chars-forward "0-9./=\\-") (skip-syntax-forward " ") (cond ((looking-at "!\\s-*") 'pending) ((looking-at "\\*\\s-*") 'cleared) (t nil))))) -(defun ledger-transaction-state () +(defun ledger-posting-state () + "Return the state of the posting." (save-excursion (goto-char (line-beginning-position)) (skip-syntax-forward " ") (cond ((looking-at "!\\s-*") 'pending) ((looking-at "\\*\\s-*") 'cleared) - (t (ledger-entry-state))))) + (t (ledger-transaction-state))))) -(defun ledger-toggle-current-transaction (&optional style) +(defun ledger-char-from-state (state) + "Return the char representation of STATE." + (if state + (if (eq state 'pending) + "!" + "*") + "")) + +(defun ledger-state-from-char (state-char) + "Get state from STATE-CHAR." + (cond ((eql state-char ?\!) 'pending) + ((eql state-char ?\*) 'cleared) + ((eql state-char ?\;) 'comment) + (t nil))) + +(defun ledger-toggle-current-posting (&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'). +`cleared'). Returns the new status as 'pending 'cleared or nil. This function is rather complicated because it must preserve both -the overall formatting of the ledger entry, as well as ensuring +the overall formatting of the ledger xact, 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 +achieved more certainly by passing the xact 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 + (let ((bounds (ledger-find-xact-extents (point))) + new-status cur-status) + ;; Uncompact the xact, 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 + (save-excursion ;; this excursion checks state of entire + ;; transaction and unclears if marked + (goto-char (car bounds)) ;; beginning of xact + (skip-chars-forward "0-9./=\\- \t") ;; skip the date + (setq cur-status (and (member (char-after) '(?\* ?\!)) + (ledger-state-from-char (char-after)))) + ;;if cur-status if !, or * then delete the marker + (when cur-status (let ((here (point))) (skip-chars-forward "*! ") (let ((width (- (point) here))) @@ -59,69 +97,78 @@ dropped." (if (search-forward " " (line-end-position) t) (insert (make-string width ? )))))) (forward-line) + ;; Shift the cleared/pending status to the postings (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 + (when (not (eq (ledger-state-from-char (char-after)) 'comment)) + (insert (ledger-char-from-state cur-status) " ") + (if (search-forward " " (line-end-position) t) + (delete-char 2))) + (forward-line)) + (setq new-status nil))) + + ;;this excursion toggles the posting status (save-excursion + (setq inhibit-modification-hooks t) + (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 - (re-search-forward "\\(\t\\| [ \t]\\)" - (line-end-position) t)) - (cond - ((looking-at "\t") - (delete-char 1)) - ((looking-at " [ \t]") - (delete-char 2)) - ((looking-at " ") - (delete-char 1)))) - (setq clear inserted))))) - ;; Clean up the entry so that it displays minimally + (skip-chars-forward " \t") + (let ((here (point)) + (cur-status (ledger-state-from-char (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 cur-status + (if (and style (eq style 'cleared)) + (progn + (insert "* ") + (setq inserted 'cleared))) + (if (and style (eq style 'pending)) + (progn + (insert "! ") + (setq inserted 'pending)) + (progn + (insert "* ") + (setq inserted 'cleared)))) + (if (and inserted + (re-search-forward "\\(\t\\| [ \t]\\)" + (line-end-position) t)) + (cond + ((looking-at "\t") + (delete-char 1)) + ((looking-at " [ \t]") + (delete-char 2)) + ((looking-at " ") + (delete-char 1)))) + (setq new-status inserted)))) + (setq inhibit-modification-hooks nil)) + + ;; This excursion cleans up the xact so that it displays + ;; minimally. This means that if all posts are cleared, remove + ;; the marks and clear the entire transaction. (save-excursion (goto-char (car bounds)) (forward-line) (let ((first t) - (state ? ) + (state nil) (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)))) + (let ((cur-status (ledger-state-from-char (char-after)))) + (if (not (eq cur-status 'comment)) + (if first + (setq state cur-status + first nil) + (if (not (eq state cur-status)) + (setq hetero t))))) (forward-line)) - (when (and (not hetero) (/= state ? )) + (when (and (not hetero) (not (eq state nil))) (goto-char (car bounds)) (forward-line) (while (looking-at "[ \t]") @@ -136,54 +183,62 @@ dropped." (insert (make-string width ? )))))) (forward-line)) (goto-char (car bounds)) - (skip-chars-forward "0-9./= \t") - (insert state " ") + (skip-chars-forward "0-9./=\\- \t") + (insert (ledger-char-from-state state) " ") + (setq new-status state) (if (re-search-forward "\\(\t\\| [ \t]\\)" (line-end-position) t) (cond - ((looking-at "\t") - (delete-char 1)) - ((looking-at " [ \t]") - (delete-char 2)) - ((looking-at " ") - (delete-char 1))))))) - clear)) + ((looking-at "\t") + (delete-char 1)) + ((looking-at " [ \t]") + (delete-char 2)) + ((looking-at " ") + (delete-char 1))))))) + new-status)) (defun ledger-toggle-current (&optional style) + "Toggle the current thing at point with optional STYLE." (interactive) - (if (or ledger-clear-whole-entries - (eq 'entry (ledger-thing-at-point))) + (if (or ledger-clear-whole-transactions + (eq 'transaction (ledger-thing-at-point))) (progn (save-excursion (forward-line) (goto-char (line-beginning-position)) (while (and (not (eolp)) (save-excursion - (not (eq 'entry (ledger-thing-at-point))))) + (not (eq 'transaction (ledger-thing-at-point))))) (if (looking-at "\\s-+[*!]") - (ledger-toggle-current-transaction nil)) + (ledger-toggle-current-posting style)) (forward-line) (goto-char (line-beginning-position)))) - (ledger-toggle-current-entry style)) - (ledger-toggle-current-transaction style))) + (ledger-toggle-current-transaction style)) + (ledger-toggle-current-posting style))) -(defun ledger-toggle-current-entry (&optional style) +(defun ledger-toggle-current-transaction (&optional style) + "Toggle the transaction at point using 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)) + (save-excursion + (when (or (looking-at "^[0-9]") + (re-search-backward "^[0-9]" nil t)) + (skip-chars-forward "0-9./=\\-") + (delete-horizontal-space) + (if (or (eq (ledger-state-from-char (char-after)) 'pending) + (eq (ledger-state-from-char (char-after)) 'cleared)) + (progn + (delete-char 1) + (when (and style (eq style 'cleared)) + (insert " *") + 'cleared)) + (if (and style (eq style 'pending)) + (progn + (insert " ! ") + 'pending) + (progn + (insert " * ") + 'cleared)))))) (provide 'ldg-state) + +;;; ldg-state.el ends here diff --git a/lisp/ldg-test.el b/lisp/ldg-test.el index 478c62d8..0c571caa 100644 --- a/lisp/ldg-test.el +++ b/lisp/ldg-test.el @@ -1,12 +1,64 @@ -(defcustom ledger-source-directory "~/src/ledger" - "Directory where the Ledger sources are located." - :type 'directory +;;; ldg-test.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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. + +(defgroup ledger-test nil + "Definitions for the Ledger testing framework" :group 'ledger) -(defcustom ledger-test-binary "~/Products/ledger/debug/ledger" +(defcustom ledger-source-directory "~/ledger/" "Directory where the Ledger sources are located." + :type 'directory + :group 'ledger-test) + +(defcustom ledger-test-binary "/Products/ledger/debug/ledger" + "Directory where the Ledger debug binary is located." :type 'file - :group 'ledger) + :group 'ledger-test) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun ledger-create-test () + "Create a regression test." + (interactive) + (save-restriction + (org-narrow-to-subtree) + (save-excursion + (let (text beg) + (goto-char (point-min)) + (forward-line 1) + (setq beg (point)) + (search-forward ":PROPERTIES:") + (goto-char (line-beginning-position)) + (setq text (buffer-substring-no-properties beg (point))) + (goto-char (point-min)) + (re-search-forward ":ID:\\s-+\\([^-]+\\)") + (find-file-other-window + (format "~/src/ledger/test/regress/%s.test" (match-string 1))) + (sit-for 0) + (insert text) + (goto-char (point-min)) + (while (not (eobp)) + (goto-char (line-beginning-position)) + (delete-char 3) + (forward-line 1)))))) (defun ledger-test-org-narrow-to-entry () (outline-back-to-heading) @@ -46,9 +98,9 @@ (ledger-mode) (if input (insert input) - (insert "2012-03-17 Payee\n") - (insert " Expenses:Food $20\n") - (insert " Assets:Cash\n")) + (insert "2012-03-17 Payee\n") + (insert " Expenses:Food $20\n") + (insert " Assets:Cash\n")) (insert "\ntest reg\n") (if output (insert output)) @@ -69,7 +121,7 @@ (let ((prev-directory default-directory)) (cd ledger-source-directory) (unwind-protect - (async-shell-command (format "\"%s\" %s" command args)) + (async-shell-command (format "\"%s\" %s" command args)) (cd prev-directory))))))) (provide 'ldg-test) diff --git a/lisp/ldg-texi.el b/lisp/ldg-texi.el index b0334099..84ba34c2 100644 --- a/lisp/ldg-texi.el +++ b/lisp/ldg-texi.el @@ -1,6 +1,37 @@ -(defvar ledger-path "/Users/johnw/bin/ledger") -(defvar ledger-sample-doc-path "/Users/johnw/src/ledger/doc/sample.dat") -(defvar ledger-normalization-args "--args-only --columns 80") +;;; ldg-texi.el --- Helper code for use with the "ledger" command-line tool + +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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. + +(defgroup ledger-texi nil +"Options for working on Ledger texi documentation" +:group 'ledger) + +(defcustom ledger-texi-sample-doc-path "~/ledger/doc/sample.dat" +"Location for sample data to be used in texi tests" +:type 'file +:group 'ledger-texi) + +(defcustom ledger-texi-normalization-args "--args-only --columns 80" +"texi normalization for producing ledger output" +:type 'string +:group 'ledger-texi) (defun ledger-update-test () (interactive) @@ -71,19 +102,19 @@ (defun ledger-texi-expand-command (command data-file) (if (string-match "\\$LEDGER" command) - (replace-match (format "%s -f \"%s\" %s" ledger-path - data-file ledger-normalization-args) t t command) - (concat (format "%s -f \"%s\" %s " ledger-path - data-file ledger-normalization-args) command))) + (replace-match (format "%s -f \"%s\" %s" ledger-binary-path + data-file ledger-texi-normalization-args) t t command) + (concat (format "%s -f \"%s\" %s " ledger-binary-path + data-file ledger-texi-normalization-args) command))) (defun ledger-texi-invoke-command (command) (with-temp-buffer (shell-command command t (current-buffer)) - (if (= (point-min) (point-max)) - (progn - (push-mark nil t) - (message "Command '%s' yielded no result at %d" command (point)) - (ding)) - (buffer-string)))) + (if (= (point-min) (point-max)) + (progn + (push-mark nil t) + (message "Command '%s' yielded no result at %d" command (point)) + (ding)) + (buffer-string)))) (defun ledger-texi-write-test-data (name input) (let ((path (expand-file-name name temporary-file-directory))) @@ -101,7 +132,7 @@ (let ((section (match-string 1)) (example-name (match-string 2)) (command (match-string 3)) expanded-command - (data-file ledger-sample-doc-path) + (data-file ledger-texi-sample-doc-path) input output) (goto-char (match-end 0)) (forward-line) @@ -128,7 +159,7 @@ (let ((section-name (if (string= section "smex") "smallexample" - "example")) + "example")) (output (ledger-texi-invoke-command (ledger-texi-expand-command command data-file)))) (insert "@" section-name ?\n output diff --git a/lisp/ldg-xact.el b/lisp/ldg-xact.el index e1f165a7..bf50dbe2 100644 --- a/lisp/ldg-xact.el +++ b/lisp/ldg-xact.el @@ -1,20 +1,189 @@ -;; A sample entry sorting function, which works if entry dates are of -;; the form YYYY/mm/dd. +;;; ldg-xact.el --- Helper code for use with the "ledger" command-line tool -(defun ledger-sort () - (interactive) +;; Copyright (C) 2003-2013 John Wiegley (johnw AT gnu DOT org) + +;; 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: +;; Utilities for running ledger synchronously. + +;;; Code: + +(defcustom ledger-highlight-xact-under-point t + "If t highlight xact under point." + :type 'boolean + :group 'ledger) + +(defvar highlight-overlay (list)) + +(defun ledger-find-xact-extents (pos) + "Return point for beginning of xact and and of xact containing position. +Requires empty line separating xacts. Argument POS is a location +within the transaction." + (interactive "d") (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))))) + (goto-char pos) + (list (progn + (backward-paragraph) + (if (/= (point) (point-min)) + (forward-line)) + (line-beginning-position)) + (progn + (forward-paragraph) + (line-beginning-position))))) + +(defun ledger-highlight-xact-under-point () + "Move the highlight overlay to the current transaction." + (if ledger-highlight-xact-under-point + (let ((exts (ledger-find-xact-extents (point))) + (ovl highlight-overlay)) + (if (not highlight-overlay) + (setq ovl + (setq highlight-overlay + (make-overlay (car exts) + (cadr exts) + (current-buffer) t nil))) + (move-overlay ovl (car exts) (cadr exts))) + (overlay-put ovl 'face 'ledger-font-xact-highlight-face) + (overlay-put ovl 'priority 100)))) + +(defun ledger-xact-payee () + "Return the payee of the transaction 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) 'xact) + (ledger-context-field-value context-info 'payee) + nil)))) + +(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-xact-find-slot (moment) + "Find the right place in the buffer for a transaction at MOMENT. +MOMENT is an encoded date" + (catch 'found + (ledger-xact-iterate-transactions (function - (lambda () - (forward-paragraph)))))) + (lambda (start date mark desc) + (if (ledger-time-less-p moment date) + (throw 'found t))))))) + +(defun ledger-xact-iterate-transactions (callback) + "Iterate through each transaction call CALLBACK for each." + (goto-char (point-min)) + (let* ((now (current-time)) + (current-year (nth 5 (decode-time now)))) + (while (not (eobp)) + (when (looking-at ledger-iterate-regex) + (let ((found-y-p (match-string 2))) + (if found-y-p + (setq current-year (string-to-number found-y-p)) ;; a Y directive was found + (let ((start (match-beginning 0)) + (year (match-string 4)) + (month (string-to-number (match-string 5))) + (day (string-to-number (match-string 6))) + (mark (match-string 7)) + (code (match-string 8)) + (desc (match-string 9))) + (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)))) + +(defsubst ledger-goto-line (line-number) + "Rapidly move point to line LINE-NUMBER." + (goto-char (point-min)) + (forward-line (1- line-number))) + + +(defun ledger-copy-transaction-at-point (date) + "Ask for a new DATE and copy the transaction under point to that date. Leave point on the first amount." + (interactive (list + (read-string "Copy to date: " + (concat ledger-year "/" ledger-month "/") 'ledger-minibuffer-history))) + (let* ((here (point)) + (extents (ledger-find-xact-extents (point))) + (transaction (buffer-substring-no-properties (car extents) (cadr extents))) + encoded-date) + (if (string-match ledger-iso-date-regexp date) + (setq encoded-date + (encode-time 0 0 0 (string-to-number (match-string 4 date)) + (string-to-number (match-string 3 date)) + (string-to-number (match-string 2 date))))) + (ledger-xact-find-slot encoded-date) + (insert transaction "\n") + (backward-paragraph 2) + (re-search-forward ledger-iso-date-regexp) + (replace-match date) + (ledger-next-amount))) + +(defun ledger-delete-current-transaction (pos) + "Delete the transaction surrounging point." + (interactive "d") + (let ((bounds (ledger-find-xact-extents pos))) + (delete-region (car bounds) (cadr bounds)))) + +(defun ledger-add-transaction (transaction-text &optional insert-at-point) + "Use ledger xact TRANSACTION-TEXT to add a transaction to the buffer. +If INSERT-AT-POINT is non-nil insert the transaction +there, otherwise call `ledger-xact-find-slot' to insert it at the +correct chronological place in the buffer." + (interactive (list + (read-string "Transaction: " (concat ledger-year "/" ledger-month "/")))) + (let* ((args (with-temp-buffer + (insert transaction-text) + (eshell-parse-arguments (point-min) (point-max)))) + (ledger-buf (current-buffer)) + exit-code) + (unless insert-at-point + (let ((date (car args))) + (if (string-match ledger-iso-date-regexp date) + (setq date + (encode-time 0 0 0 (string-to-number (match-string 4 date)) + (string-to-number (match-string 3 date)) + (string-to-number (match-string 2 date))))) + (ledger-xact-find-slot date))) + (if (> (length args) 1) + (save-excursion + (insert + (with-temp-buffer + (setq exit-code + (apply #'ledger-exec-ledger ledger-buf (current-buffer) "xact" + (mapcar 'eval args))) + (goto-char (point-min)) + (if (looking-at "Error: ") + (error (concat "Error in ledger-add-transaction: " (buffer-string))) + (buffer-string))) + "\n")) + (progn + (insert (car args) " \n\n") + (end-of-line -1))))) + + +(provide 'ldg-xact) +;;; ldg-xact.el ends here diff --git a/lisp/ledger.el b/lisp/ledger.el deleted file mode 100644 index 4fc21d6a..00000000 --- a/lisp/ledger.el +++ /dev/null @@ -1,1340 +0,0 @@ -;;; ledger.el --- Helper code for use with the "ledger" command-line tool - -;; Copyright (C) 2003-2009 John Wiegley (johnw AT gnu DOT org) - -;; Emacs Lisp Archive Entry -;; Filename: ledger.el -;; Version: 2.6.3 -;; Date: Fri 18-Jul-2008 -;; 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-e toggle cleared status of an entry -;; 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). -;; -;; 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.3" - "The version of ledger.el currently loaded") - -(defgroup ledger nil - "Interface to the Ledger command-line accounting program." - :group 'data) - -(defcustom ledger-binary-path "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 '%(<name>)' where -<name> 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 - '(("\\( \\| \\|^\\)\\(;.*\\)" 2 font-lock-comment-face) - ("^[0-9]+[-/.=][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\(\\( ;\\| ;\\|$\\)\\)" 2 bold) - ;;("^[0-9]+[-/.=][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([*].+?\\)\\(\\( ;\\| ;\\|$\\)\\)" - ;; 2 font-lock-type-face) - ("^\\s-+\\([*]\\s-*\\)?\\(\\([[(]\\)?\\([^*;]\\)+?\\(:\\|\\s-\\)[^]); - ]+?\\([])]\\)?\\)\\( \\| \\|$\\)" - 2 font-lock-keyword-face) - ("^\\([~=].+\\)" 1 font-lock-function-name-face) - ("^\\([A-Za-z]+ .+\\)" 1 font-lock-function-name-face)) - "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 &optional insert-at-point) - (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)))) - (ledger-buf (current-buffer)) - exit-code) - (unless insert-at-point - (let ((date (car args))) - (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 - (insert - (with-temp-buffer - (setq exit-code - (apply #'ledger-run-ledger ledger-buf "entry" - (mapcar 'eval args))) - (goto-char (point-min)) - (if (looking-at "Error: ") - (error (buffer-string)) - (buffer-string))) - "\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-move-to-next-field () - (re-search-forward "\\( \\|\t\\)" (line-end-position) t)) - -(defun ledger-toggle-state (state &optional style) - (if (not (null state)) - (if (and style (eq style 'cleared)) - 'cleared) - (if (and style (eq style 'pending)) - 'pending - 'cleared))) - -(defun ledger-entry-state () - (save-excursion - (when (or (looking-at "^[0-9]") - (re-search-backward "^[0-9]" nil t)) - (skip-chars-forward "0-9./=") - (skip-syntax-forward " ") - (cond ((looking-at "!\\s-*") 'pending) - ((looking-at "\\*\\s-*") 'cleared) - (t nil))))) - -(defun ledger-transaction-state () - (save-excursion - (goto-char (line-beginning-position)) - (skip-syntax-forward " ") - (cond ((looking-at "!\\s-*") 'pending) - ((looking-at "\\*\\s-*") 'cleared) - (t (ledger-entry-state))))) - -(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 - (re-search-forward "\\(\t\\| [ \t]\\)" - (line-end-position) t)) - (cond - ((looking-at "\t") - (delete-char 1)) - ((looking-at " [ \t]") - (delete-char 2)) - ((looking-at " ") - (delete-char 1)))) - (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 (re-search-forward "\\(\t\\| [ \t]\\)" - (line-end-position) t) - (insert (make-string width ? )))))) - (forward-line)) - (goto-char (car bounds)) - (skip-chars-forward "0-9./= \t") - (insert state " ") - (if (re-search-forward "\\(\t\\| [ \t]\\)" - (line-end-position) t) - (cond - ((looking-at "\t") - (delete-char 1)) - ((looking-at " [ \t]") - (delete-char 2)) - ((looking-at " ") - (delete-char 1))))))) - clear)) - -(defun ledger-toggle-current (&optional style) - (interactive) - (if (or ledger-clear-whole-entries - (eq 'entry (ledger-thing-at-point))) - (progn - (save-excursion - (forward-line) - (goto-char (line-beginning-position)) - (while (and (not (eolp)) - (save-excursion - (not (eq 'entry (ledger-thing-at-point))))) - (if (looking-at "\\s-+[*!]") - (ledger-toggle-current-transaction nil)) - (forward-line) - (goto-char (line-beginning-position)))) - (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. - -\\{ledger-mode-map}" - (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 ?e)] 'ledger-toggle-current-entry) - (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 (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin")) - (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-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 (or (equal (car where) "<stdin>") (equal (car where) "/dev/stdin")) - (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 (markerp (cdr 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) - (or (equal (car where) "<stdin>") - (equal (car where) "/dev/stdin"))) - (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" "--real" - "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 - (save-excursion - (goto-line (nth 1 item)) - (point-marker)) - (save-excursion - (goto-line (nth 0 xact)) - (point-marker))))))) - (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) - (interactive "sAccount to reconcile: ") - (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)))) - -(defvar ledger-reconcile-mode-abbrev-table) - -(defvar ledger-reconcile-mode-map - (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 ?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 [?s] 'ledger-reconcile-save) - (define-key map [?q] 'ledger-reconcile-quit) - map)) - -(define-derived-mode ledger-reconcile-mode text-mode "Reconcile" - "A mode for reconciling ledger entries. - -\\{ledger-reconcile-mode-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) - -(defvar ledger-report-mode-map - (let ((map (make-sparse-keymap))) - (define-key map [? ] 'scroll-up) - (define-key map [backspace] 'scroll-down) - (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) - map)) - -(define-derived-mode ledger-report-mode text-mode "Ledger-Report" - "A mode for viewing ledger reports.") - -(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) - (set-buffer-modified-p nil) - (setq buffer-read-only t) - (message "q to quit; r to redo; e to edit; k to kill; s to save; SPC and DEL to scroll")))) - -(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" ledger-report-name) - (format "Command: %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) - (setq buffer-read-only nil) - (erase-buffer) - (ledger-do-report ledger-report-cmd) - (setq buffer-read-only nil)) - -(defun ledger-report-quit () - "Quit the ledger report buffer by burying it." - (interactive) - (ledger-report-goto) - (set-window-configuration ledger-original-window-cfg) - (bury-buffer (get-buffer ledger-report-buffer-name))) - -(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) - ((looking-at "^\\(sun\\|mon\\|tue\\|wed\\|thu\\|fri\\|sat\\)\\s-+") - (goto-char (match-end 0)) - 'entry) - (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) - (if (null current-prefix-arg) - (ledger-entries) ; this completes against entry names - (progn - (let ((text (buffer-substring (line-beginning-position) - (line-end-position)))) - (delete-region (line-beginning-position) - (line-end-position)) - (condition-case err - (ledger-add-entry text t) - ((error) - (insert text)))) - (forward-line) - (goto-char (line-end-position)) - (search-backward ";" (line-beginning-position) t) - (skip-chars-backward " \t0123456789.,") - (throw 'pcompleted t))) - (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-next-amount (&optional end) - (when (re-search-forward "\\( \\|\t\\| \t\\)[ \t]*-?\\([A-Z$€£]+ *\\)?\\(-?[0-9,]+?\\)\\(.[0-9]+\\)?\\( *[A-Z$€£]+\\)?\\([ \t]*@@?[^\n;]+?\\)?\\([ \t]+;.+?\\)?$" (marker-position end) t) - (goto-char (match-beginning 0)) - (skip-syntax-forward " ") - (- (or (match-end 4) - (match-end 3)) (point)))) - -(defun ledger-align-amounts (&optional column) - "Align amounts in the current region. -This is done so that the last digit falls in COLUMN, which defaults to 52." - (interactive "p") - (if (or (null column) (= column 1)) - (setq column 52)) - (save-excursion - (let* ((mark-first (< (mark) (point))) - (begin (if mark-first (mark) (point))) - (end (if mark-first (point-marker) (mark-marker))) - offset) - (goto-char begin) - (while (setq offset (ledger-next-amount end)) - (let ((col (current-column)) - (target-col (- column offset)) - adjust) - (setq adjust (- target-col col)) - (if (< col target-col) - (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)))))) - -(defalias 'ledger-align-dollars 'ledger-align-amounts) - -;; 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" - ;; Let's try again, just in case they moved it while we were sleeping. - (cond - ((null ledger-binary-path) - (error "The variable `ledger-binary-path' has not been set")) - (t - (let ((buf (current-buffer))) - (with-current-buffer buffer - (let ((coding-system-for-write 'utf-8) - (coding-system-for-read 'utf-8)) - (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))) - -(easy-menu-define ledger-menu ledger-mode-map - "Ledger menu" - '("Ledger" - ["New entry" ledger-add-entry t] - ["Toggle cleared status of current entry" ledger-toggle-current-entry t] - ["Set default year for entry" ledger-set-year t] - ["Set default month for entry" ledger-set-month t] - "--" - ["Reconcile uncleared entries for account" ledger-reconcile t] - "--" - "Reports" - ["Run a report" ledger-report t] - ["Go to report buffer" ledger-report-goto t] - ["Edit defined reports" ledger-report-edit t] - ["Save report definition" ledger-report-save t] - ["Re-run ledger report" ledger-report-redo t] - ["Kill report buffer" ledger-report-kill t])) - -(provide 'ledger) - -;;; ledger.el ends here diff --git a/lisp/timeclock.el b/lisp/timeclock.el deleted file mode 100644 index 2cafa8eb..00000000 --- a/lisp/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 <johnw@gnu.org> -;; 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 "<p>")) - (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 "<br>\n<b>") - (insert "\n*")) - (if (timeclock-currently-in-p) - (insert "Working on ")) - (if html-p - (insert project "</b><br>\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 "<p>")) - (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 "<br>")) - (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 "<p>")) - (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 "</p>")) - (when html-p - (insert "<p> -<table> -<td width=\"25\"><br></td><td> -<table border=1 cellpadding=3> -<tr><th><i>Statistics</i></th> - <th>Entire</th> - <th>-30 days</th> - <th>-3 mons</th> - <th>-6 mons</th> - <th>-1 year</th> -</tr>") - (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 "<tr>\n") - (insert "<td align=\"center\">Time in</td>\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "<td align=\"right\">" - (timeclock-seconds-to-string (aref time-in i)) - "</td>\n") - (setq i (1+ i)))) - (insert "</tr>\n") - - (insert "<tr>\n") - (insert "<td align=\"center\">Time out</td>\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "<td align=\"right\">" - (timeclock-seconds-to-string (aref time-out i)) - "</td>\n") - (setq i (1+ i)))) - (insert "</tr>\n") - - (insert "<tr>\n") - (insert "<td align=\"center\">Break</td>\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "<td align=\"right\">" - (timeclock-seconds-to-string (aref breaks i)) - "</td>\n") - (setq i (1+ i)))) - (insert "</tr>\n") - - (insert "<tr>\n") - (insert "<td align=\"center\">Workday</td>\n") - (let ((i 0) (l 5)) - (while (< i l) - (insert "<td align=\"right\">" - (timeclock-seconds-to-string (aref workday i)) - "</td>\n") - (setq i (1+ i)))) - (insert "</tr>\n")) - (insert "<tfoot> -<td colspan=\"6\" align=\"center\"> - <i>These are approximate figures</i></td> -</tfoot> -</table> -</td></table>"))))) - -;;; 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/src/account.cc b/src/account.cc index 72709f95..c3fc80f1 100644 --- a/src/account.cc +++ b/src/account.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -380,7 +380,9 @@ expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind, break; case 'd': - if (fn_name == "depth") + if (fn_name == "date") + return WRAP_FUNCTOR(get_wrapper<&get_latest>); + else if (fn_name == "depth") return WRAP_FUNCTOR(get_wrapper<&get_depth>); else if (fn_name == "depth_spacer") return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>); @@ -690,12 +692,10 @@ void account_t::xdata_t::details_t::update(post_t& post, } } -void put_account(property_tree::ptree& pt, const account_t& acct, +void put_account(property_tree::ptree& st, const account_t& acct, function<bool(const account_t&)> pred) { if (pred(acct)) { - property_tree::ptree& st(pt.put("account", "")); - std::ostringstream buf; buf.width(sizeof(unsigned long) * 2); buf.fill('0'); @@ -707,18 +707,15 @@ void put_account(property_tree::ptree& pt, const account_t& acct, st.put("fullname", acct.fullname()); value_t total = acct.amount(); - if (! total.is_null()) { - property_tree::ptree& t(st.put("account-amount", "")); - put_value(t, total); - } + if (! total.is_null()) + put_value(st.put("account-amount", ""), total); + total = acct.total(); - if (! total.is_null()) { - property_tree::ptree& t(st.put("account-total", "")); - put_value(t, total); - } + if (! total.is_null()) + put_value(st.put("account-total", ""), total); foreach (const accounts_map::value_type& pair, acct.accounts) - put_account(st, *pair.second, pred); + put_account(st.add("account", ""), *pair.second, pred); } } diff --git a/src/account.h b/src/account.h index a2fcb8de..7b53de15 100644 --- a/src/account.h +++ b/src/account.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -308,6 +308,13 @@ std::ostream& operator<<(std::ostream& out, const account_t& account); void put_account(property_tree::ptree& pt, const account_t& acct, function<bool(const account_t&)> pred); +//simple struct added to allow std::map to compare accounts in the accounts report +struct account_compare { + bool operator() (const account_t& lhs, const account_t& rhs){ + return (lhs.fullname().compare(rhs.fullname()) < 0); + } +}; + } // namespace ledger #endif // _ACCOUNT_H diff --git a/src/amount.cc b/src/amount.cc index 6ecb3558..55cbabeb 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -30,6 +30,7 @@ */ #include <system.hh> +#include <math.h> #include "amount.h" #include "commodity.h" @@ -195,7 +196,10 @@ namespace { for (const char * p = buf; *p; p++) { if (*p == '.') { - if (commodity_t::decimal_comma_by_default || + if (commodity_t::time_colon_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_TIME_COLON))) + out << ':'; + else if (commodity_t::decimal_comma_by_default || (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA))) out << ','; else @@ -209,7 +213,10 @@ namespace { out << *p; if (integer_digits > 3 && --integer_digits % 3 == 0) { - if (commodity_t::decimal_comma_by_default || + if (commodity_t::time_colon_by_default || + (comm && comm->has_flags(COMMODITY_STYLE_TIME_COLON))) + out << ':'; + else if (commodity_t::decimal_comma_by_default || (comm && comm->has_flags(COMMODITY_STYLE_DECIMAL_COMMA))) out << '.'; else @@ -666,14 +673,31 @@ void amount_t::in_place_truncate() void amount_t::in_place_floor() { if (! quantity) - throw_(amount_error, _("Cannot floor an uninitialized amount")); + throw_(amount_error, _("Cannot compute floor on an uninitialized amount")); _dup(); - std::ostringstream out; - stream_out_mpq(out, MP(quantity), precision_t(0), -1, GMP_RNDZ); + mpz_fdiv_q(temp, mpq_numref(MP(quantity)), mpq_denref(MP(quantity))); + mpq_set_z(MP(quantity), temp); +} + +void amount_t::in_place_ceiling() +{ + if (! quantity) + throw_(amount_error, _("Cannot compute ceiling on an uninitialized amount")); + + _dup(); - mpq_set_str(MP(quantity), out.str().c_str(), 10); + mpz_cdiv_q(temp, mpq_numref(MP(quantity)), mpq_denref(MP(quantity))); + mpq_set_z(MP(quantity), temp); +} + +void amount_t::in_place_roundto(int places) +{ + if (! quantity) + throw_(amount_error, _("Cannot round an uninitialized amount")); + double x=ceil(mpq_get_d(MP(quantity))*pow(10, places) - 0.49999999) / pow(10, places); + mpq_set_d(MP(quantity), x); } void amount_t::in_place_unround() @@ -720,6 +744,16 @@ void amount_t::in_place_unreduce() } if (shifted) { + if ("h" == comm->symbol() && commodity_t::time_colon_by_default) { + amount_t floored = tmp.floored(); + amount_t precision = tmp - floored; + if (precision < 0.0) { + precision += 1.0; + floored -= 1.0; + } + tmp = floored + (precision * (comm->smaller()->number() / 100.0)); + } + *this = tmp; commodity_ = comm; } @@ -1073,6 +1107,9 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) bool decimal_comma_style = (commodity_t::decimal_comma_by_default || commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA)); + bool time_colon_style + = (commodity_t::time_colon_by_default || + commodity().has_flags(COMMODITY_STYLE_TIME_COLON)); new_quantity->prec = 0; @@ -1283,13 +1320,11 @@ bool amount_t::valid() const return true; } -void put_amount(property_tree::ptree& pt, const amount_t& amt, - bool wrap, bool commodity_details) +void put_amount(property_tree::ptree& st, const amount_t& amt, + bool commodity_details) { - property_tree::ptree& st(wrap ? pt.put("amount", "") : pt); - if (amt.has_commodity()) - put_commodity(st, amt.commodity(), commodity_details); + put_commodity(st.put("commodity", ""), amt.commodity(), commodity_details); st.put("quantity", amt.quantity_string()); } diff --git a/src/amount.h b/src/amount.h index 49027bb7..938d4b7b 100644 --- a/src/amount.h +++ b/src/amount.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -346,6 +346,13 @@ public: } void in_place_round(); + amount_t roundto(int places) const { + amount_t temp(*this); + temp.in_place_round(); + return temp; + } + void in_place_roundto(int places); + /** Yields an amount which has lost all of its extra precision, beyond what the display precision of the commodity would have printed. */ amount_t truncated() const { @@ -364,6 +371,15 @@ public: } void in_place_floor(); + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t ceilinged() const { + amount_t temp(*this); + temp.in_place_ceiling(); + return temp; + } + void in_place_ceiling(); + /** Yields an amount whose display precision is never truncated, even though its commodity normally displays only rounded values. */ amount_t unrounded() const { @@ -777,7 +793,7 @@ inline std::istream& operator>>(std::istream& in, amount_t& amt) { } void put_amount(property_tree::ptree& pt, const amount_t& amt, - bool wrap = true, bool commodity_details = false); + bool commodity_details = false); } // namespace ledger diff --git a/src/annotate.cc b/src/annotate.cc index b7b6b5cb..8816a89c 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -216,19 +216,13 @@ void annotation_t::print(std::ostream& out, bool keep_base, out << " ((" << *value_expr << "))"; } -void put_annotation(property_tree::ptree& pt, const annotation_t& details) +void put_annotation(property_tree::ptree& st, const annotation_t& details) { - property_tree::ptree& st(pt.put("annotation", "")); + if (details.price) + put_amount(st.put("price", ""), *details.price); - if (details.price) { - property_tree::ptree& t(st.put("price", "")); - put_amount(t, *details.price, false); - } - - if (details.date) { - property_tree::ptree& t(st.put("date", "")); - put_date(t, *details.date, false); - } + if (details.date) + put_date(st.put("date", ""), *details.date); if (details.tag) st.put("tag", *details.tag); diff --git a/src/annotate.h b/src/annotate.h index 27deaad3..998f7c83 100644 --- a/src/annotate.h +++ b/src/annotate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/archive.cc b/src/archive.cc index caeaf965..6ac1c580 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/archive.h b/src/archive.h index ee4e9ccb..5c15a9fb 100644 --- a/src/archive.h +++ b/src/archive.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/balance.cc b/src/balance.cc index d9551670..b702cb7b 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -336,12 +336,10 @@ void balance_t::print(std::ostream& out, amount_printer.close(); } -void put_balance(property_tree::ptree& pt, const balance_t& bal) +void put_balance(property_tree::ptree& st, const balance_t& bal) { - property_tree::ptree& st(pt.put("balance", "")); - foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) - put_amount(st, pair.second); + put_amount(st.add("amount", ""), pair.second); } } // namespace ledger diff --git a/src/balance.h b/src/balance.h index 230a4e2c..a1ae51ef 100644 --- a/src/balance.h +++ b/src/balance.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -325,6 +325,17 @@ public: pair.second.in_place_round(); } + balance_t roundto(int places) const { + balance_t temp(*this); + temp.in_place_roundto(places); + return temp; + } + + void in_place_roundto(int places) { + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_roundto(places); + } + balance_t truncated() const { balance_t temp(*this); temp.in_place_truncate(); @@ -345,6 +356,17 @@ public: pair.second.in_place_floor(); } + balance_t ceilinged() const { + balance_t temp(*this); + temp.in_place_ceiling(); + return temp; + } + void in_place_ceiling() { + foreach (amounts_map::value_type& pair, amounts) + pair.second.in_place_ceiling(); + } + + balance_t unrounded() const { balance_t temp(*this); temp.in_place_unround(); diff --git a/src/chain.cc b/src/chain.cc index 52d52f14..b2f6f203 100644 --- a/src/chain.cc +++ b/src/chain.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/chain.h b/src/chain.h index 15ae12ba..de7f68c7 100644 --- a/src/chain.h +++ b/src/chain.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/commodity.cc b/src/commodity.cc index 05d465ca..535b31c9 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -40,6 +40,7 @@ namespace ledger { bool commodity_t::decimal_comma_by_default = false; +bool commodity_t::time_colon_by_default = false; void commodity_t::add_price(const datetime_t& date, const amount_t& price, const bool reflexive) @@ -496,11 +497,9 @@ bool commodity_t::compare_by_commodity::operator()(const amount_t * left, } } -void put_commodity(property_tree::ptree& pt, const commodity_t& comm, +void put_commodity(property_tree::ptree& st, const commodity_t& comm, bool commodity_details) { - property_tree::ptree& st(pt.put("commodity", "")); - std::string flags; if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) flags += 'P'; if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) flags += 'S'; @@ -511,7 +510,7 @@ void put_commodity(property_tree::ptree& pt, const commodity_t& comm, st.put("symbol", comm.symbol()); if (commodity_details && comm.has_annotation()) - put_annotation(st, as_annotated_commodity(comm).details); + put_annotation(st.put("annotation", ""), as_annotated_commodity(comm).details); } } // namespace ledger diff --git a/src/commodity.h b/src/commodity.h index ab496850..a1988a3b 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -107,6 +107,7 @@ protected: #define COMMODITY_SAW_ANNOTATED 0x200 #define COMMODITY_SAW_ANN_PRICE_FLOAT 0x400 #define COMMODITY_SAW_ANN_PRICE_FIXATED 0x800 +#define COMMODITY_STYLE_TIME_COLON 0x1000 string symbol; optional<std::size_t> graph_index; @@ -176,6 +177,7 @@ protected: public: static bool decimal_comma_by_default; + static bool time_colon_by_default; virtual ~commodity_t() { TRACE_DTOR(commodity_t); @@ -349,6 +351,13 @@ inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { void put_commodity(property_tree::ptree& pt, const commodity_t& comm, bool commodity_details = false); +//simple struct to allow std::map to compare commodities names +struct commodity_compare { + bool operator() (const commodity_t* lhs, const commodity_t* rhs){ + return (lhs->symbol().compare(rhs->symbol()) < 0); + } +}; + } // namespace ledger #endif // _COMMODITY_H diff --git a/src/compare.cc b/src/compare.cc index e2a298c2..946cd835 100644 --- a/src/compare.cc +++ b/src/compare.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/compare.h b/src/compare.h index e1abbca1..4a91f49f 100644 --- a/src/compare.h +++ b/src/compare.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/context.h b/src/context.h index 09734b3e..e5457641 100644 --- a/src/context.h +++ b/src/context.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -112,16 +112,16 @@ inline parse_context_t open_for_reading(const path& pathname, const path& cwd) { path filename = resolve_path(pathname); - - if (! exists(filename)) - throw_(std::runtime_error, - _f("Cannot read journal file %1%") % filename); - #if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 - path parent(filesystem::absolute(pathname, cwd).parent_path()); + filename = filesystem::absolute(filename, cwd); #else - path parent(filesystem::complete(pathname, cwd).parent_path()); + filename = filesystem::complete(filename, cwd); #endif + if (! exists(filename) || is_directory(filename)) + throw_(std::runtime_error, + _f("Cannot read journal file %1%") % filename); + + path parent(filename.parent_path()); shared_ptr<std::istream> stream(new ifstream(filename)); parse_context_t context(stream, parent); context.pathname = filename; diff --git a/src/convert.cc b/src/convert.cc index f0d8db06..38ef2c22 100644 --- a/src/convert.cc +++ b/src/convert.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/convert.h b/src/convert.h index de958108..17fb4505 100644 --- a/src/convert.h +++ b/src/convert.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -245,7 +245,7 @@ xact_t * csv_reader::read_xact(bool rich_data) // Translate the account name, if we have enough information to do so - foreach (account_mapping_t& value, context.journal->account_mappings) { + foreach (account_mapping_t& value, context.journal->payees_for_unknown_accounts) { if (value.first.match(xact->payee)) { post->account = value.second; break; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/draft.cc b/src/draft.cc index a6f60520..6aef2f12 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/draft.h b/src/draft.h index 9023e6da..1cc362ba 100644 --- a/src/draft.h +++ b/src/draft.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/emacs.cc b/src/emacs.cc index 41c67cc6..9bfd1e4b 100644 --- a/src/emacs.cc +++ b/src/emacs.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/emacs.h b/src/emacs.h index a018ce68..368c4c4d 100644 --- a/src/emacs.h +++ b/src/emacs.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/error.cc b/src/error.cc index 58339db7..acee85c9 100644 --- a/src/error.cc +++ b/src/error.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -84,7 +84,7 @@ string source_context(const path& file, const string& prefix) { const std::streamoff len = end_pos - pos; - if (! len || file == path("/dev/stdin")) + if (! len || file.empty()) return _("<no source context>"); assert(len > 0); diff --git a/src/error.h b/src/error.h index 9837fa4d..93d92109 100644 --- a/src/error.h +++ b/src/error.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/expr.cc b/src/expr.cc index dbe860bf..aa1e07d2 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/exprbase.h b/src/exprbase.h index 4edf2a7a..88d6f118 100644 --- a/src/exprbase.h +++ b/src/exprbase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/filters.cc b/src/filters.cc index f5694133..9b8e4fc6 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -530,9 +530,11 @@ bool display_filter_posts::output_rounding(post_t& post) } // Allow the posting to be displayed if: - // 1. It's display_amount would display as non-zero - // 2. The --empty option was specified - // 3. The account of the posting is <Revalued> + // 1. Its display_amount would display as non-zero, or + // 2. The --empty option was specified, or + // 3. a) The account of the posting is <Revalued>, and + // b) the revalued option is specified, and + // c) the --no-rounding option is not specified. if (post.account == revalued_account) { if (show_rounding) @@ -981,7 +983,8 @@ void interval_posts::flush() sort_posts_by_date()); // Determine the beginning interval by using the earliest post - if (! interval.find_period(all_posts.front()->date())) + if (all_posts.front() && + ! interval.find_period(all_posts.front()->date())) throw_(std::logic_error, _("Failed to find period for interval report")); // Walk the interval forward reporting all posts within each one diff --git a/src/filters.h b/src/filters.h index b765f630..42ff2e5c 100644 --- a/src/filters.h +++ b/src/filters.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/flags.h b/src/flags.h index 77a2c4dd..d584e2d0 100644 --- a/src/flags.h +++ b/src/flags.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/format.cc b/src/format.cc index d29c87b3..e0f74616 100644 --- a/src/format.cc +++ b/src/format.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -129,7 +129,7 @@ format_t::element_t * format_t::parse_elements(const string& fmt, element_t * current = NULL; - char buf[1024]; + static char buf[65535]; char * q = buf; for (const char * p = fmt.c_str(); *p; p++) { diff --git a/src/format.h b/src/format.h index cc48bdda..38f567bd 100644 --- a/src/format.h +++ b/src/format.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/generate.cc b/src/generate.cc index bcbde9f1..2b471c87 100644 --- a/src/generate.cc +++ b/src/generate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/generate.h b/src/generate.h index 1b22004b..f171605b 100644 --- a/src/generate.h +++ b/src/generate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/global.cc b/src/global.cc index a718d6cb..a4da6ead 100644 --- a/src/global.cc +++ b/src/global.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -44,6 +44,7 @@ namespace ledger { static bool args_only = false; +std::string _init_file; global_scope_t::global_scope_t(char ** envp) { @@ -106,30 +107,53 @@ global_scope_t::~global_scope_t() #endif } +void global_scope_t::parse_init(path init_file) +{ + TRACE_START(init, 1, "Read initialization file"); + + parse_context_stack_t parsing_context; + parsing_context.push(init_file); + parsing_context.get_current().journal = session().journal.get(); + parsing_context.get_current().scope = &report(); + + if (session().journal->read(parsing_context) > 0 || + session().journal->auto_xacts.size() > 0 || + session().journal->period_xacts.size() > 0) { + throw_(parse_error, _f("Transactions found in initialization file '%1%'") + % init_file); + } + + TRACE_FINISH(init, 1); +} + void global_scope_t::read_init() { + // if specified on the command line init_file_ is filled in + // global_scope_t::handle_debug_options. If it was specified on the command line + // fail is the file doesn't exist. If no init file was specified + // on the command-line then try the default values, but don't fail if there + // isn't one. + path init_file; if (HANDLED(init_file_)) { - path init_file(HANDLER(init_file_).str()); + init_file=HANDLER(init_file_).str(); if (exists(init_file)) { - TRACE_START(init, 1, "Read initialization file"); - - parse_context_stack_t parsing_context; - parsing_context.push(init_file); - parsing_context.get_current().journal = session().journal.get(); - parsing_context.get_current().scope = &report(); - - if (session().journal->read(parsing_context) > 0 || - session().journal->auto_xacts.size() > 0 || - session().journal->period_xacts.size() > 0) { - throw_(parse_error, _f("Transactions found in initialization file '%1%'") - % init_file); - } - - TRACE_FINISH(init, 1); + parse_init(init_file); + } else { + throw_(parse_error, _f("Could not find specified init file %1%") % init_file); } + } else { + if (const char * home_var = std::getenv("HOME")){ + init_file = (path(home_var) / ".ledgerrc"); + } else { + init_file = ("./.ledgerrc"); + } + } + if(exists(init_file)){ + parse_init(init_file); } } + char * global_scope_t::prompt_string() { static char prompt[32]; @@ -473,6 +497,10 @@ void handle_debug_options(int argc, char * argv[]) _log_level = LOG_INFO; #endif } + else if (i + 1 < argc && std::strcmp(argv[i], "--init-file") == 0) { + _init_file = argv[i + 1]; + i++; + } else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { #if DEBUG_ON _log_level = LOG_DEBUG; diff --git a/src/global.h b/src/global.h index f797ba01..c024dce2 100644 --- a/src/global.h +++ b/src/global.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -46,6 +46,8 @@ namespace ledger { class session_t; class report_t; +extern std::string _init_file; + class global_scope_t : public noncopyable, public scope_t { shared_ptr<session_t> session_ptr; @@ -65,6 +67,7 @@ public: return _("global scope"); } + void parse_init(path init_file); void read_init(); void read_environment_settings(char * envp[]); strings_list read_command_arguments(scope_t& scope, strings_list args); @@ -124,7 +127,7 @@ public: out << '-' << Ledger_VERSION_DATE; out << _(", the command-line accounting tool"); out << - _("\n\nCopyright (c) 2003-2012, John Wiegley. All rights reserved.\n\n\ + _("\n\nCopyright (c) 2003-2013, John Wiegley. All rights reserved.\n\n\ This program is made available under the terms of the BSD Public License.\n\ See LICENSE file included with the distribution for details and disclaimer."); out << std::endl; @@ -151,10 +154,9 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION__ (global_scope_t, init_file_, // -i CTOR(global_scope_t, init_file_) { - if (const char * home_var = std::getenv("HOME")) - on(none, (path(home_var) / ".ledgerrc").string()); - else - on(none, path("./.ledgerrc").string()); + if (!_init_file.empty()) + // _init_file is filled during handle_debug_options + on(none, _init_file); }); OPTION(global_scope_t, options); diff --git a/src/history.cc b/src/history.cc index 25335680..dde8c441 100644 --- a/src/history.cc +++ b/src/history.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -129,6 +129,10 @@ commodity_history_t::commodity_history_t() p_impl.reset(new commodity_history_impl_t); } +commodity_history_t::~commodity_history_t() +{ +} + void commodity_history_t::add_commodity(commodity_t& comm) { p_impl->add_commodity(comm); diff --git a/src/history.h b/src/history.h index 4362c9f9..baefb333 100644 --- a/src/history.h +++ b/src/history.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -88,6 +88,7 @@ public: const datetime_t& oldest = datetime_t()); void print_map(std::ostream& out, const datetime_t& moment = datetime_t()); + ~commodity_history_t(); }; } // namespace ledger diff --git a/src/item.cc b/src/item.cc index 0630043b..a29a3fd3 100644 --- a/src/item.cc +++ b/src/item.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -330,6 +330,21 @@ namespace { return NULL_VALUE; } + value_t get_filebase(item_t& item) { + if (item.pos) + return string_value(item.pos->pathname.filename().string()); + else + return NULL_VALUE; + } + + value_t get_filepath(item_t& item) { + if (item.pos) + return string_value(item.pos->pathname.parent_path().string()); + else + return NULL_VALUE; + } + + value_t get_beg_pos(item_t& item) { return item.pos ? long(item.pos->beg_pos) : 0L; } @@ -456,7 +471,11 @@ expr_t::ptr_op_t item_t::lookup(const symbol_t::kind_t kind, case 'f': if (name == "filename") return WRAP_FUNCTOR(get_wrapper<&get_pathname>); - break; + else if (name == "filebase") + return WRAP_FUNCTOR(get_wrapper<&get_filebase>); + else if (name == "filepath") + return WRAP_FUNCTOR(get_wrapper<&get_filepath>); + break; case 'h': if (name == "has_tag") @@ -563,8 +582,8 @@ string item_context(const item_t& item, const string& desc) std::ostringstream out; - if (item.pos->pathname == path("/dev/stdin")) { - out << desc << _(" from standard input:"); + if (item.pos->pathname.empty()) { + out << desc << _(" from streamed input:"); return out.str(); } @@ -581,16 +600,15 @@ string item_context(const item_t& item, const string& desc) return out.str(); } -void put_metadata(property_tree::ptree& pt, const item_t::string_map& metadata) +void put_metadata(property_tree::ptree& st, const item_t::string_map& metadata) { - property_tree::ptree& st(pt.put("metadata", "")); foreach (const item_t::string_map::value_type& pair, metadata) { if (pair.second.first) { - property_tree::ptree& vt(st.put("pair", "")); - vt.put("key", pair.first); + property_tree::ptree& vt(st.add("value", "")); + vt.put("<xmlattr>.key", pair.first); put_value(vt, *pair.second.first); } else { - st.put("tag", pair.first); + st.add("tag", pair.first); } } } @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/iterators.cc b/src/iterators.cc index 0a053031..738f33be 100644 --- a/src/iterators.cc +++ b/src/iterators.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/iterators.h b/src/iterators.h index 53814666..3ece313c 100644 --- a/src/iterators.h +++ b/src/iterators.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/journal.cc b/src/journal.cc index e6c09125..c4c8fd98 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -127,8 +127,19 @@ account_t * journal_t::register_account(const string& name, post_t * post, // object. if (account_aliases.size() > 0) { accounts_map::const_iterator i = account_aliases.find(name); - if (i != account_aliases.end()) + if (i != account_aliases.end()) { result = (*i).second; + } else { + // only check the very first account for alias expansion, in case + // that can be expanded successfully + size_t colon = name.find(':'); + if(colon != string::npos) { + accounts_map::const_iterator i = account_aliases.find(name.substr(0, colon)); + if (i != account_aliases.end()) { + result = find_account((*i).second->fullname() + name.substr(colon)); + } + } + } } // Create the account object and associate it with the journal; this diff --git a/src/journal.h b/src/journal.h index 5e0fa0e3..716e2910 100644 --- a/src/journal.h +++ b/src/journal.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/lookup.cc b/src/lookup.cc index 9edc3bbc..2a602569 100644 --- a/src/lookup.cc +++ b/src/lookup.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/lookup.h b/src/lookup.h index ba64b0b5..ee80faac 100644 --- a/src/lookup.h +++ b/src/lookup.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/main.cc b/src/main.cc index a1ac0339..accae197 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -59,6 +59,7 @@ int main(int argc, char * argv[], char * envp[]) // --debug CATEGORY ; turns on debug logging // --trace LEVEL ; turns on trace logging // --memory ; turns on memory usage tracing + // --init-file ; directs ledger to use a different init file handle_debug_options(argc, argv); #if VERIFY_ON IF_VERIFY() initialize_memory_tracing(); @@ -213,7 +214,8 @@ int main(int argc, char * argv[], char * envp[]) } else #endif { - global_scope->quick_close(); + if (global_scope) + global_scope->quick_close(); INFO("Ledger ended"); // let global_scope leak! } diff --git a/src/mask.cc b/src/mask.cc index 18d482ed..56ef02d1 100644 --- a/src/mask.cc +++ b/src/mask.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -151,7 +151,7 @@ inline std::ostream& operator<<(std::ostream& out, const mask_t& mask) { } inline void put_mask(property_tree::ptree& pt, const mask_t& mask) { - pt.put("mask", mask.str()); + pt.put_value(mask.str()); } } // namespace ledger @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/option.cc b/src/option.cc index 7fcc9e4d..256ee45b 100644 --- a/src/option.cc +++ b/src/option.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/option.h b/src/option.h index 642142aa..fe3e257b 100644 --- a/src/option.h +++ b/src/option.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/output.cc b/src/output.cc index f433f8d1..77d28eed 100644 --- a/src/output.cc +++ b/src/output.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -318,6 +318,34 @@ void report_payees::operator()(post_t& post) (*i).second++; } +void report_tags::flush() +{ + std::ostream& out(report.output_stream); + + foreach (tags_pair& entry, tags) { + if (report.HANDLED(count)) + out << entry.second << ' '; + out << entry.first << '\n'; + } +} + +void report_tags::operator()(post_t& post) +{ + if(post.metadata){ + foreach (const item_t::string_map::value_type& data, *post.metadata){ + string tag=data.first; + if(report.HANDLED(values) && (data.second).first){ + tag+=": "+ (data.second).first.get().to_string(); + } + std::map<string, std::size_t>::iterator i = tags.find(tag); + if (i == tags.end()) + tags.insert(tags_pair(tag, 1)); + else + (*i).second++; + } + } +} + void report_commodities::flush() { std::ostream& out(report.output_stream); diff --git a/src/output.h b/src/output.h index 281f69b6..31afbd13 100644 --- a/src/output.h +++ b/src/output.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -142,7 +142,7 @@ class report_accounts : public item_handler<post_t> protected: report_t& report; - std::map<account_t *, std::size_t> accounts; + std::map<account_t *, std::size_t, account_compare> accounts; typedef std::map<account_t *, std::size_t>::value_type accounts_pair; @@ -189,12 +189,39 @@ public: } }; +class report_tags : public item_handler<post_t> +{ +protected: + report_t& report; + + std::map<string, std::size_t> tags; + + typedef std::map<string, std::size_t>::value_type tags_pair; + +public: + report_tags(report_t& _report) : report(_report) { + TRACE_CTOR(report_tags, "report&"); + } + virtual ~report_tags() { + TRACE_DTOR(report_tags); + } + + virtual void flush(); + virtual void operator()(post_t& post); + + virtual void clear() { + tags.clear(); + item_handler<post_t>::clear(); + } +}; + + class report_commodities : public item_handler<post_t> { protected: report_t& report; - std::map<commodity_t *, std::size_t> commodities; + std::map<commodity_t *, std::size_t, commodity_compare> commodities; typedef std::map<commodity_t *, std::size_t>::value_type commodities_pair; diff --git a/src/parser.cc b/src/parser.cc index a17ad271..bcb23f48 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/parser.h b/src/parser.h index 9e6a59f4..8cc8027f 100644 --- a/src/parser.h +++ b/src/parser.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/pool.cc b/src/pool.cc index 9f1aea9f..b9e64c4b 100644 --- a/src/pool.cc +++ b/src/pool.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/post.cc b/src/post.cc index f23b81cb..1a24429b 100644 --- a/src/post.cc +++ b/src/post.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -696,10 +696,8 @@ void extend_post(post_t& post, journal_t& journal) } } -void put_post(property_tree::ptree& pt, const post_t& post) +void put_post(property_tree::ptree& st, const post_t& post) { - property_tree::ptree& st(pt.put("posting", "")); - if (post.state() == item_t::CLEARED) st.put("<xmlattr>.state", "cleared"); else if (post.state() == item_t::PENDING) @@ -710,14 +708,10 @@ void put_post(property_tree::ptree& pt, const post_t& post) if (post.has_flags(ITEM_GENERATED)) st.put("<xmlattr>.generated", "true"); - if (post._date) { - property_tree::ptree& t(st.put("date", "")); - put_date(t, *post._date, false); - } - if (post._date_aux) { - property_tree::ptree& t(st.put("aux-date", "")); - put_date(t, *post._date_aux, false); - } + if (post._date) + put_date(st.put("date", ""), *post._date); + if (post._date_aux) + put_date(st.put("aux-date", ""), *post._date_aux); if (post.account) { property_tree::ptree& t(st.put("account", "")); @@ -736,34 +730,27 @@ void put_post(property_tree::ptree& pt, const post_t& post) if (post.has_xdata() && post.xdata().has_flags(POST_EXT_COMPOUND)) put_value(t, post.xdata().compound_value); else - put_amount(t, post.amount); + put_amount(t.put("amount", ""), post.amount); } - if (post.cost) { - property_tree::ptree& t(st.put("cost", "")); - put_amount(t, *post.cost, false); - } + if (post.cost) + put_amount(st.put("cost", ""), *post.cost); if (post.assigned_amount) { - if (post.has_flags(POST_CALCULATED)) { - property_tree::ptree& t(st.put("balance-assertion", "")); - put_amount(t, *post.assigned_amount, false); - } else { - property_tree::ptree& t(st.put("balance-assignment", "")); - put_amount(t, *post.assigned_amount, false); - } + if (post.has_flags(POST_CALCULATED)) + put_amount(st.put("balance-assertion", ""), *post.assigned_amount); + else + put_amount(st.put("balance-assignment", ""), *post.assigned_amount); } if (post.note) st.put("note", *post.note); if (post.metadata) - put_metadata(st, *post.metadata); + put_metadata(st.put("metadata", ""), *post.metadata); - if (post.xdata_ && ! post.xdata_->total.is_null()) { - property_tree::ptree& t(st.put("total", "")); - put_value(t, post.xdata_->total); - } + if (post.xdata_ && ! post.xdata_->total.is_null()) + put_value(st.put("total", ""), post.xdata_->total); } } // namespace ledger @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -83,7 +83,7 @@ public: const optional<string>& _note = none) : item_t(_flags, _note), xact(NULL), account(_account), amount(_amount) { - TRACE_CTOR(post_t, "account_t *, const amount_t&, flags_t, const optional<string>&"); + TRACE_CTOR(post_t, "account_t *, amount_t, flags_t, optional<string>"); } post_t(const post_t& post) : item_t(post), diff --git a/src/precmd.cc b/src/precmd.cc index fe0836bc..e9c966e9 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/precmd.h b/src/precmd.h index 1c52d8a7..27925f42 100644 --- a/src/precmd.h +++ b/src/precmd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/predicate.h b/src/predicate.h index c670d6a4..6a1cc01c 100644 --- a/src/predicate.h +++ b/src/predicate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/print.cc b/src/print.cc index 79d83161..f04f59d5 100644 --- a/src/print.cc +++ b/src/print.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -203,9 +203,15 @@ namespace { (static_cast<std::string::size_type>(account_width) - static_cast<std::string::size_type>(name.length())); + std::size_t amount_width = + (report.HANDLED(amount_width_) ? + lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) : + 12); string amt; if (post->amount_expr) { - amt = post->amount_expr->text(); + std::ostringstream amt_str; + justify(amt_str, post->amount_expr->text(), amount_width, true); + amt = amt_str.str(); } else if (count == 2 && index == 2 && post_has_simple_amount(*post) && @@ -218,11 +224,6 @@ namespace { // first. } else { - std::size_t amount_width = - (report.HANDLED(amount_width_) ? - lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) : - 12); - std::ostringstream amt_str; value_t(post->amount).print(amt_str, static_cast<int>(amount_width), -1, AMOUNT_PRINT_RIGHT_JUSTIFY | diff --git a/src/print.h b/src/print.h index 42bfc8b6..8d78e8cc 100644 --- a/src/print.h +++ b/src/print.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/pstream.h b/src/pstream.h index e9cddb4c..4886dc0b 100644 --- a/src/pstream.h +++ b/src/pstream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions diff --git a/src/ptree.cc b/src/ptree.cc index 6fb840e0..e7afdcd1 100644 --- a/src/ptree.cc +++ b/src/ptree.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -61,28 +61,28 @@ void format_ptree::flush() property_tree::ptree& ct(pt.put("ledger.commodities", "")); foreach (const commodities_pair& pair, commodities) - put_commodity(ct, *pair.second, true); + put_commodity(ct.add("commodity", ""), *pair.second, true); property_tree::ptree& at(pt.put("ledger.accounts", "")); - put_account(at, *report.session.journal->master, account_visited_p); + put_account(at.add("account", ""), *report.session.journal->master, account_visited_p); property_tree::ptree& tt(pt.put("ledger.transactions", "")); foreach (const xact_t * xact, transactions) { - put_xact(tt, *xact); + property_tree::ptree& t(tt.add("transaction", "")); + put_xact(t, *xact); - property_tree::ptree& post_tree(tt.put("postings", "")); + property_tree::ptree& post_tree(t.put("postings", "")); foreach (const post_t * post, xact->posts) if (post->has_xdata() && post->xdata().has_flags(POST_EXT_VISITED)) - put_post(post_tree, *post); + put_post(post_tree.add("posting", ""), *post); } switch (format) { case FORMAT_XML: - property_tree::write_xml(out, pt); - break; - case FORMAT_JSON: - property_tree::write_json(out, pt); + property_tree::xml_writer_settings<char> indented(' ', 2); + property_tree::write_xml(out, pt, indented); + out << std::endl; break; } } diff --git a/src/ptree.h b/src/ptree.h index b28f97d5..154c8837 100644 --- a/src/ptree.h +++ b/src/ptree.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -75,8 +75,7 @@ protected: public: enum format_t { - FORMAT_XML, - FORMAT_JSON + FORMAT_XML } format; format_ptree(report_t& _report, format_t _format = FORMAT_XML) diff --git a/src/py_account.cc b/src/py_account.cc index 64a7ae54..fbd68140 100644 --- a/src/py_account.cc +++ b/src/py_account.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_amount.cc b/src/py_amount.cc index 0aa8fee8..50e16a70 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_balance.cc b/src/py_balance.cc index 2ae546f1..65e3c401 100644 --- a/src/py_balance.cc +++ b/src/py_balance.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_commodity.cc b/src/py_commodity.cc index b283efcc..44a45e33 100644 --- a/src/py_commodity.cc +++ b/src/py_commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_expr.cc b/src/py_expr.cc index dd9df1f5..c680bab4 100644 --- a/src/py_expr.cc +++ b/src/py_expr.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_format.cc b/src/py_format.cc index 482eaf5b..43266b29 100644 --- a/src/py_format.cc +++ b/src/py_format.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_item.cc b/src/py_item.cc index 893ddcfa..02b978c5 100644 --- a/src/py_item.cc +++ b/src/py_item.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_journal.cc b/src/py_journal.cc index 50a52be9..7cce6b11 100644 --- a/src/py_journal.cc +++ b/src/py_journal.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_post.cc b/src/py_post.cc index 692542a0..6061d6ee 100644 --- a/src/py_post.cc +++ b/src/py_post.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_session.cc b/src/py_session.cc index f411d5e1..7e2f9e8a 100644 --- a/src/py_session.cc +++ b/src/py_session.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_times.cc b/src/py_times.cc index 17f9ec7e..599aa60c 100644 --- a/src/py_times.cc +++ b/src/py_times.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_utils.cc b/src/py_utils.cc index 45ffe545..dc572621 100644 --- a/src/py_utils.cc +++ b/src/py_utils.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_value.cc b/src/py_value.cc index b931f008..8b565661 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/py_xact.cc b/src/py_xact.cc index 3d792c7b..0b07f582 100644 --- a/src/py_xact.cc +++ b/src/py_xact.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -119,8 +119,8 @@ void export_xact() .def("__str__", py_xact_to_string) .add_property("code", - make_getter(&xact_t::code), - make_setter(&xact_t::code)) + make_getter(&xact_t::code, return_value_policy<return_by_value>()), + make_setter(&xact_t::code, return_value_policy<return_by_value>())) .add_property("payee", make_getter(&xact_t::payee), make_setter(&xact_t::payee)) @@ -157,6 +157,8 @@ void export_xact() make_getter(&period_xact_t::period_string), make_setter(&period_xact_t::period_string)) ; + + register_optional_to_python<std::string>(); } } // namespace ledger diff --git a/src/pyfstream.h b/src/pyfstream.h index 18f28bc4..12bbecfd 100644 --- a/src/pyfstream.h +++ b/src/pyfstream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/pyinterp.cc b/src/pyinterp.cc index 135a088b..3354d3e9 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/pyinterp.h b/src/pyinterp.h index 56f808c8..e961f4c0 100644 --- a/src/pyinterp.h +++ b/src/pyinterp.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/pyledger.cc b/src/pyledger.cc index cf5e362e..ee7b99c9 100644 --- a/src/pyledger.cc +++ b/src/pyledger.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/pyutils.h b/src/pyutils.h index 406ca1ee..6bb9d0bd 100644 --- a/src/pyutils.h +++ b/src/pyutils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/query.cc b/src/query.cc index 07f724fd..209205ae 100644 --- a/src/query.cc +++ b/src/query.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/query.h b/src/query.h index fe52eb35..10820a59 100644 --- a/src/query.h +++ b/src/query.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/quotes.cc b/src/quotes.cc index e92af236..2df51ec1 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/quotes.h b/src/quotes.h index 56740e47..813bdfa3 100644 --- a/src/quotes.h +++ b/src/quotes.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/report.cc b/src/report.cc index 662386a4..29077f10 100644 --- a/src/report.cc +++ b/src/report.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -681,11 +681,21 @@ value_t report_t::fn_floor(call_scope_t& args) return args[0].floored(); } +value_t report_t::fn_ceiling(call_scope_t& args) +{ + return args[0].ceilinged(); +} + value_t report_t::fn_round(call_scope_t& args) { return args[0].rounded(); } +value_t report_t::fn_roundto(call_scope_t& args) +{ + return args[0].roundto(args.get<int>(1)); +} + value_t report_t::fn_unround(call_scope_t& args) { return args[0].unrounded(); @@ -1084,6 +1094,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(anon); else OPT_ALT(color, ansi); else OPT(auto_match); + else OPT(aux_date); else OPT(average); else OPT(account_width_); else OPT(amount_width_); @@ -1091,7 +1102,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) case 'b': OPT(balance_format_); else OPT(base); - else OPT_ALT(basis, cost); + else OPT(basis); else OPT_(begin_); else OPT(bold_if_); else OPT(budget); @@ -1100,6 +1111,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) break; case 'c': OPT(csv_format_); + else OPT_ALT(gain, change); else OPT(cleared); else OPT(collapse); else OPT(collapse_if_zero); @@ -1117,6 +1129,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(dc); else OPT(depth_); else OPT(deviation); + else OPT_ALT(rich_data, detail); else OPT_(display_); else OPT(display_amount_); else OPT(display_total_); @@ -1141,7 +1154,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT_ALT(head_, first_); break; case 'g': - OPT_ALT(gain, change); + OPT(gain); else OPT(group_by_); else OPT(group_title_format_); else OPT(generated); @@ -1168,7 +1181,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT_ALT(tail_, last_); break; case 'm': - OPT_ALT(market, value); + OPT(market); else OPT(monthly); else OPT(meta_); else OPT(meta_width_); @@ -1198,6 +1211,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(price); else OPT(prices_format_); else OPT(pricedb_format_); + else OPT(primary_date); else OPT(payee_width_); else OPT(prepend_format_); else OPT(prepend_width_); @@ -1215,7 +1229,7 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(revalued); else OPT(revalued_only); else OPT(revalued_total_); - else OPT_ALT(rich_data, detail); + else OPT(rich_data); break; case 's': OPT(sort_); @@ -1242,6 +1256,10 @@ option_t<report_t> * report_t::lookup_option(const char * p) else OPT(unrealized_losses_); else OPT(unround); break; + case 'v': + OPT_ALT(market, value); + else OPT(values); + break; case 'w': OPT(weekly); else OPT_(wide); @@ -1335,6 +1353,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(fn_cyan); else if (is_eq(p, "commodity")) return MAKE_FUNCTOR(report_t::fn_commodity); + else if (is_eq(p, "ceiling")) + return MAKE_FUNCTOR(report_t::fn_ceiling); break; case 'd': @@ -1420,6 +1440,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return WRAP_FUNCTOR(fn_red); else if (is_eq(p, "round")) return MAKE_FUNCTOR(report_t::fn_round); + else if (is_eq(p, "roundto")) + return MAKE_FUNCTOR(report_t::fn_roundto); break; case 's': @@ -1585,7 +1607,11 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, return POSTS_REPORTER(new report_commodities(*this)); } break; - + case 'd': + if (is_eq(p, "draft")) { + return WRAP_FUNCTOR(xact_command); + } + break; case 'e': if (is_eq(p, "equity")) { HANDLER(generated).on("#equity"); @@ -1602,12 +1628,10 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, } break; - case 'j': - if (is_eq(p, "json")) - return POSTS_REPORTER(new format_ptree(*this, - format_ptree::FORMAT_JSON)); + case 'l': + if (is_eq(p, "lisp")) + return POSTS_REPORTER(new format_emacs_posts(output_stream)); break; - case 'o': if (is_eq(p, "org")) return POSTS_REPORTER(new posts_to_org_table @@ -1649,7 +1673,11 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind, else if (is_eq(p, "select")) return WRAP_FUNCTOR(select_command); break; - + case 't': + if (is_eq(p, "tags")) { + return POSTS_REPORTER(new report_tags(*this)); + } + break; case 'x': if (is_eq(p, "xact")) return WRAP_FUNCTOR(xact_command); diff --git a/src/report.h b/src/report.h index 0f4fc103..5897e8f6 100644 --- a/src/report.h +++ b/src/report.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -123,7 +123,7 @@ public: TRACE_CTOR(report_t, "session_t&"); } report_t(const report_t& report) - : session(report.session), + : scope_t(report), session(report.session), output_stream(report.output_stream), terminus(report.terminus), budget_flags(report.budget_flags) { @@ -174,7 +174,9 @@ public: value_t fn_unrounded(call_scope_t& scope); value_t fn_truncated(call_scope_t& scope); value_t fn_floor(call_scope_t& scope); + value_t fn_ceiling(call_scope_t& scope); value_t fn_round(call_scope_t& scope); + value_t fn_roundto(call_scope_t& scope); value_t fn_unround(call_scope_t& scope); value_t fn_abs(call_scope_t& scope); value_t fn_justify(call_scope_t& scope); @@ -357,6 +359,7 @@ public: HANDLER(account_width_).report(out); HANDLER(amount_width_).report(out); HANDLER(total_width_).report(out); + HANDLER(values).report(out); } option_t<report_t> * lookup_option(const char * p); @@ -1042,6 +1045,7 @@ public: OPTION(report_t, account_width_); OPTION(report_t, amount_width_); OPTION(report_t, total_width_); + OPTION(report_t, values); }; template <class Type = post_t, diff --git a/src/scope.cc b/src/scope.cc index 9112040c..10ae45a9 100644 --- a/src/scope.cc +++ b/src/scope.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/scope.h b/src/scope.h index 4190f5bb..d6291439 100644 --- a/src/scope.h +++ b/src/scope.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/select.cc b/src/select.cc index afcfb351..b7e4c920 100644 --- a/src/select.cc +++ b/src/select.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/select.h b/src/select.h index 54883d22..346067a2 100644 --- a/src/select.h +++ b/src/select.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/session.cc b/src/session.cc index b6153203..e5425a50 100644 --- a/src/session.cc +++ b/src/session.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -62,12 +62,7 @@ void set_session_context(session_t * session) session_t::session_t() : flush_on_next_data_file(false), journal(new journal_t) { - if (const char * home_var = std::getenv("HOME")) - HANDLER(price_db_).on(none, (path(home_var) / ".pricedb").string()); - else - HANDLER(price_db_).on(none, path("./.pricedb").string()); - - parsing_context.push(); + parsing_context.push(); TRACE_CTOR(session_t, ""); } @@ -98,8 +93,18 @@ std::size_t session_t::read_data(const string& master_account) acct = journal->find_account(master_account); optional<path> price_db_path; - if (HANDLED(price_db_)) + if (HANDLED(price_db_)){ price_db_path = resolve_path(HANDLER(price_db_).str()); + if (!exists(price_db_path.get())){ + throw_(parse_error, _f("Could not find specified price-db file %1%") % price_db_path); + } + } else { + if (const char * home_var = std::getenv("HOME")){ + price_db_path = (path(home_var) / ".pricedb"); + } else { + price_db_path = ("./.ledgerrc"); + } + } if (HANDLED(explicit)) journal->force_checking = true; @@ -143,7 +148,7 @@ std::size_t session_t::read_data(const string& master_account) } foreach (const path& pathname, HANDLER(file_).data_files) { - if (pathname == "-") { + if (pathname == "-" || pathname == "/dev/stdin") { // To avoid problems with stdin and pipes, etc., we read the entire // file in beforehand into a memory buffer, and then parcel it out // from there. @@ -348,9 +353,11 @@ option_t<session_t> * session_t::lookup_option(const char * p) case 's': OPT(strict); break; + case 't': + OPT(time_colon); + break; case 'v': OPT(value_expr_); - break; } return NULL; } diff --git a/src/session.h b/src/session.h index a0aba91b..21be3cc7 100644 --- a/src/session.h +++ b/src/session.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -100,6 +100,7 @@ public: HANDLER(day_break).report(out); HANDLER(download).report(out); HANDLER(decimal_comma).report(out); + HANDLER(time_colon).report(out); HANDLER(file_).report(out); HANDLER(input_date_format_).report(out); HANDLER(explicit).report(out); @@ -130,6 +131,10 @@ public: commodity_t::decimal_comma_by_default = true; }); + OPTION_(session_t, time_colon, DO() { + commodity_t::time_colon_by_default = true; + }); + OPTION__ (session_t, price_exp_, // -Z CTOR(session_t, price_exp_) { value = "24"; }); diff --git a/src/stats.cc b/src/stats.cc index a12420b6..81d71a46 100644 --- a/src/stats.cc +++ b/src/stats.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/stats.h b/src/stats.h index 7b00fec8..5583e704 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/stream.cc b/src/stream.cc index ce40bfcc..ad03ab77 100644 --- a/src/stream.cc +++ b/src/stream.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/stream.h b/src/stream.h index c317ebdf..bb63873a 100644 --- a/src/stream.h +++ b/src/stream.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/system.hh.in b/src/system.hh.in index e7411a5e..ff7ea75b 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -204,7 +204,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp> -#include <boost/property_tree/json_parser.hpp> #include <boost/random/mersenne_twister.hpp> #include <boost/random/uniform_int.hpp> diff --git a/src/temps.cc b/src/temps.cc index 881077f6..b4778b49 100644 --- a/src/temps.cc +++ b/src/temps.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/temps.h b/src/temps.h index daa1493b..99110496 100644 --- a/src/temps.h +++ b/src/temps.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/textual.cc b/src/textual.cc index a5ae2f68..932bde84 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -148,7 +148,7 @@ namespace { void account_value_directive(account_t * account, string expr_str); void account_default_directive(account_t * account); - void default_account_directive(char * line); + void default_account_directive(char * args); void alias_directive(char * line); void payee_directive(char * line); @@ -282,6 +282,10 @@ void instance_t::parse() } } +#if defined(TIMELOG_SUPPORT) + timelog.close(); +#endif // TIMELOG_SUPPORT + TRACE_STOP(instance_parse, 1); } @@ -397,7 +401,7 @@ void instance_t::read_next_directive(bool& error_flag) #endif // TIMELOG_SUPPORT case 'A': // a default account for unbalanced posts - default_account_directive(line); + default_account_directive(line + 1); break; case 'C': // a set of conversions price_conversion_directive(line); @@ -492,7 +496,7 @@ void instance_t::default_commodity_directive(char * line) void instance_t::default_account_directive(char * line) { - context.journal->bucket = top_account()->find_account(skip_ws(line + 1)); + context.journal->bucket = top_account()->find_account(skip_ws(line)); context.journal->bucket->add_flags(ACCOUNT_KNOWN); } @@ -532,9 +536,8 @@ void instance_t::option_directive(char * line) *p++ = '\0'; } - path abs_path(filesystem::absolute(context.pathname, - context.current_directory)); - if (! process_option(abs_path.string(), line + 2, *context.scope, p, line)) + if (! process_option(context.pathname.string(), line + 2, *context.scope, + p, line)) throw_(option_error, _f("Illegal option --%1%") % (line + 2)); } @@ -576,7 +579,7 @@ void instance_t::automated_xact_directive(char * line) item = ae.get(); // This is a trailing note, and possibly a metadata info tag - item->append_note(p + 1, *context.scope, true); + ae->append_note(p + 1, *context.scope, true); item->add_flags(ITEM_NOTE_ON_NEXT_LINE); item->pos->end_pos = context.curr_pos; item->pos->end_line++; diff --git a/src/timelog.cc b/src/timelog.cc index 9516ba17..64017d50 100644 --- a/src/timelog.cc +++ b/src/timelog.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -169,10 +169,8 @@ void time_log_t::close() foreach (account_t * account, accounts) { DEBUG("timelog", "Clocking out from account " << account->fullname()); - clock_out_from_timelog(time_xacts, - time_xact_t(none, CURRENT_TIME(), account), - context); - context.count++; + context.count += clock_out_from_timelog + (time_xacts, time_xact_t(none, CURRENT_TIME(), account), context); } assert(time_xacts.empty()); } diff --git a/src/timelog.h b/src/timelog.h index a902c084..1ce0be28 100644 --- a/src/timelog.h +++ b/src/timelog.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/times.cc b/src/times.cc index 167aac73..f9a6c279 100644 --- a/src/times.cc +++ b/src/times.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/times.h b/src/times.h index 9a0f21ff..4b89cc0c 100644 --- a/src/times.h +++ b/src/times.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -111,20 +111,12 @@ std::string format_date(const date_t& when, void set_date_format(const char * format); void set_input_date_format(const char * format); -inline void put_datetime(property_tree::ptree& pt, const datetime_t& when, - bool wrap = true) { - if (wrap) - pt.put("datetime", format_datetime(when, FMT_WRITTEN)); - else - pt.put_value(format_datetime(when, FMT_WRITTEN)); +inline void put_datetime(property_tree::ptree& pt, const datetime_t& when) { + pt.put_value(format_datetime(when, FMT_WRITTEN)); } -inline void put_date(property_tree::ptree& pt, const date_t& when, - bool wrap = true) { - if (wrap) - pt.put("date", format_date(when, FMT_WRITTEN)); - else - pt.put_value(format_date(when, FMT_WRITTEN)); +inline void put_date(property_tree::ptree& pt, const date_t& when) { + pt.put_value(format_date(when, FMT_WRITTEN)); } struct date_traits_t diff --git a/src/token.cc b/src/token.cc index 1c43af32..d0ec185b 100644 --- a/src/token.cc +++ b/src/token.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/token.h b/src/token.h index 01ff7ee9..51fa1d78 100644 --- a/src/token.h +++ b/src/token.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/unistring.h b/src/unistring.h index b2278796..eb38f8b1 100644 --- a/src/unistring.h +++ b/src/unistring.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/utils.cc b/src/utils.cc index 1a82787d..2f592325 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -153,6 +153,8 @@ std::size_t current_memory_size() return memory_size; } +//#if !defined(__has_feature) || !__has_feature(address_sanitizer) + static void trace_new_func(void * ptr, const char * which, std::size_t size) { if (! live_memory || ! memory_tracing_active) return; @@ -221,8 +223,12 @@ static void trace_delete_func(void * ptr, const char * which) memory_tracing_active = true; } +//#endif // !defined(__has_feature) || !__has_feature(address_sanitizer) + } // namespace ledger +//#if !defined(__has_feature) || !__has_feature(address_sanitizer) + void * operator new(std::size_t size) throw (std::bad_alloc) { void * ptr = std::malloc(size); if (DO_VERIFY() && ledger::memory_tracing_active) @@ -268,6 +274,8 @@ void operator delete[](void * ptr, const std::nothrow_t&) throw() { std::free(ptr); } +//#endif // !defined(__has_feature) || !__has_feature(address_sanitizer) + namespace ledger { namespace { diff --git a/src/utils.h b/src/utils.h index 1b356e7c..c4d11636 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/value.cc b/src/value.cc index 1921d5a3..98e48c2f 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -1612,6 +1612,27 @@ void value_t::in_place_round() throw_(value_error, _f("Cannot set rounding for %1%") % label()); } +void value_t::in_place_roundto(int places) +{ + DEBUG("amount.roundto", "=====> roundto places " << places); + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_roundto(places); + return; + case BALANCE: + as_balance_lval().in_place_roundto(places); + return; + case SEQUENCE: + foreach (value_t& value, as_sequence_lval()) + value.in_place_roundto(places); + return; + default: + break; + } +} + void value_t::in_place_truncate() { switch (type()) { @@ -1658,6 +1679,29 @@ void value_t::in_place_floor() throw_(value_error, _f("Cannot floor %1%") % label()); } +void value_t::in_place_ceiling() +{ + switch (type()) { + case INTEGER: + return; + case AMOUNT: + as_amount_lval().in_place_ceiling(); + return; + case BALANCE: + as_balance_lval().in_place_ceiling(); + return; + case SEQUENCE: + foreach (value_t& value, as_sequence_lval()) + value.in_place_ceiling(); + return; + default: + break; + } + + add_error_context(_f("While ceiling %1%:") % *this); + throw_(value_error, _f("Cannot ceiling %1%") % label()); +} + void value_t::in_place_unround() { switch (type()) { @@ -1803,7 +1847,7 @@ void value_t::print(std::ostream& _out, switch (type()) { case VOID: - out << "(null)"; + out << ""; break; case BOOLEAN: @@ -2018,35 +2062,35 @@ void put_value(property_tree::ptree& pt, const value_t& value) { switch (value.type()) { case value_t::VOID: - pt.put("void", ""); + pt.add("void", ""); break; case value_t::BOOLEAN: - pt.put("bool", value.as_boolean() ? "true" : "false"); + pt.add("bool", value.as_boolean() ? "true" : "false"); break; case value_t::INTEGER: - pt.put("int", value.to_string()); + pt.add("int", value.to_string()); break; case value_t::AMOUNT: - put_amount(pt, value.as_amount()); + put_amount(pt.add("amount", ""), value.as_amount()); break; case value_t::BALANCE: - put_balance(pt, value.as_balance()); + put_balance(pt.add("balance", ""), value.as_balance()); break; case value_t::DATETIME: - put_datetime(pt, value.as_datetime()); + put_datetime(pt.add("datetime", ""), value.as_datetime()); break; case value_t::DATE: - put_date(pt, value.as_date()); + put_date(pt.add("date", ""), value.as_date()); break; case value_t::STRING: - pt.put("string", value.as_string()); + pt.add("string", value.as_string()); break; case value_t::MASK: - put_mask(pt, value.as_mask()); + put_mask(pt.add("mask", ""), value.as_mask()); break; case value_t::SEQUENCE: { - property_tree::ptree& st(pt.put("sequence", "")); + property_tree::ptree& st(pt.add("sequence", "")); foreach (const value_t& member, value.as_sequence()) put_value(st, member); break; diff --git a/src/value.h b/src/value.h index ee3d414d..93760234 100644 --- a/src/value.h +++ b/src/value.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -443,6 +443,13 @@ public: } void in_place_round(); + value_t roundto(int places) const { + value_t temp(*this); + temp.in_place_roundto(places); + return temp; + } + void in_place_roundto(int places); + value_t truncated() const { value_t temp(*this); temp.in_place_truncate(); @@ -457,6 +464,13 @@ public: } void in_place_floor(); + value_t ceilinged() const { + value_t temp(*this); + temp.in_place_ceiling(); + return temp; + } + void in_place_ceiling(); + value_t unrounded() const { value_t temp(*this); temp.in_place_unround(); diff --git a/src/views.cc b/src/views.cc index 76d06370..2646e84b 100644 --- a/src/views.cc +++ b/src/views.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/views.h b/src/views.h index 7f769250..94bedda5 100644 --- a/src/views.h +++ b/src/views.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/src/xact.cc b/src/xact.cc index b5cb2a38..8aecf5d6 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 @@ -36,6 +36,7 @@ #include "account.h" #include "journal.h" #include "context.h" +#include "format.h" #include "pool.h" namespace ledger { @@ -355,14 +356,16 @@ bool xact_base_t::finalize() } } } else { - if (post->amount.has_annotation()) { - if (breakdown.amount.has_annotation()) - breakdown.amount.annotation().tag = post->amount.annotation().tag; - else - breakdown.amount.annotate - (annotation_t(none, none, post->amount.annotation().tag)); - } - post->amount = breakdown.amount; + post->amount = + breakdown.amount.has_annotation() ? + amount_t(breakdown.amount, + annotation_t(breakdown.amount.annotation().price, + breakdown.amount.annotation().date, + post->amount.has_annotation() ? + post->amount.annotation().tag : + breakdown.amount.annotation().tag, + breakdown.amount.annotation().value_expr)) : + breakdown.amount; DEBUG("xact.finalize", "added breakdown, balance = " << balance); } @@ -642,6 +645,18 @@ namespace { } } +static string apply_format(const string& str, scope_t& scope) +{ + if (contains(str, "%(")) { + format_t str_format(str); + std::ostringstream buf; + buf << str_format(scope); + return buf.str(); + } else { + return str; + } +} + void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) { posts_list initial_posts(xact.posts.begin(), xact.posts.end()); @@ -693,8 +708,9 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { if (data.apply_to_post == NULL) - initial_post->parse_tags(data.tag_data.c_str(), bound_scope, - data.overwrite_existing); + initial_post->append_note( + apply_format(data.tag_data, bound_scope).c_str(), + bound_scope, data.overwrite_existing); } } @@ -773,11 +789,27 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) account = account->parent; account = account->find_account(fullname); } + else if (contains(fullname, "%(")) { + format_t account_name(fullname); + std::ostringstream buf; + buf << account_name(bound_scope); + while (account->parent) + account = account->parent; + account = account->find_account(buf.str()); + } // Copy over details so that the resulting post is a mirror of // the automated xact's one. post_t * new_post = new post_t(account, amt); new_post->copy_details(*post); + + // A Cleared transaction implies all of its automatic posting are cleared + // CPR 2012/10/23 + if (xact.state() == item_t::CLEARED) { + DEBUG("xact.extend.cleared", "CLEARED"); + new_post->set_state(item_t::CLEARED); + } + new_post->add_flags(ITEM_GENERATED); new_post->account = journal->register_account(account->fullname(), new_post, @@ -785,9 +817,11 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) if (deferred_notes) { foreach (deferred_tag_data_t& data, *deferred_notes) { - if (! data.apply_to_post || data.apply_to_post == post) - new_post->parse_tags(data.tag_data.c_str(), bound_scope, - data.overwrite_existing); + if (! data.apply_to_post || data.apply_to_post == post) { + new_post->append_note( + apply_format(data.tag_data, bound_scope).c_str(), + bound_scope, data.overwrite_existing); + } } } @@ -813,10 +847,8 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context) } } -void put_xact(property_tree::ptree& pt, const xact_t& xact) +void put_xact(property_tree::ptree& st, const xact_t& xact) { - property_tree::ptree& st(pt.put("transaction", "")); - if (xact.state() == item_t::CLEARED) st.put("<xmlattr>.state", "cleared"); else if (xact.state() == item_t::PENDING) @@ -825,14 +857,10 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact) if (xact.has_flags(ITEM_GENERATED)) st.put("<xmlattr>.generated", "true"); - if (xact._date) { - property_tree::ptree& t(st.put("date", "")); - put_date(t, *xact._date, false); - } - if (xact._date_aux) { - property_tree::ptree& t(st.put("aux-date", "")); - put_date(t, *xact._date_aux, false); - } + if (xact._date) + put_date(st.put("date", ""), *xact._date); + if (xact._date_aux) + put_date(st.put("aux-date", ""), *xact._date_aux); if (xact.code) st.put("code", *xact.code); @@ -843,7 +871,7 @@ void put_xact(property_tree::ptree& pt, const xact_t& xact) st.put("note", *xact.note); if (xact.metadata) - put_metadata(st, *xact.metadata); + put_metadata(st.put("metadata", ""), *xact.metadata); } } // namespace ledger @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * Copyright (c) 2003-2013, 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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b5d8cf09..94ce0a0a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,7 +22,7 @@ macro(add_ledger_harness_tests _class) foreach(TestFile ${${_class}_TESTS}) get_filename_component(TestFile_Name ${TestFile} NAME_WE) string(FIND ${TestFile_Name} "_py" TestFile_IsPythonTest) - if((NOT TestFile_IsPythonTest) OR HAVE_BOOST_PYTHON) + if((TestFile_IsPythonTest EQUAL -1) OR HAVE_BOOST_PYTHON) add_test(${_class}Test_${TestFile_Name} ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/RegressTests.py ${LEDGER_LOCATION} ${PROJECT_SOURCE_DIR} diff --git a/test/LedgerHarness.py b/test/LedgerHarness.py index b8900971..39a31286 100755 --- a/test/LedgerHarness.py +++ b/test/LedgerHarness.py @@ -116,12 +116,14 @@ class LedgerHarness: def success(self): sys.stdout.write(".") + sys.stdout.flush() self.succeeded += 1 def failure(self, name=None): sys.stdout.write("E") if name: sys.stdout.write("[%s]" % name) + sys.stdout.flush() self.failed += 1 def exit(self): diff --git a/test/RegressTests.py b/test/RegressTests.py index 7d67eb21..01e14191 100755 --- a/test/RegressTests.py +++ b/test/RegressTests.py @@ -98,8 +98,10 @@ class RegressFile(object): def run_test(self, test): use_stdin = False - if test['command'].find("-f - ") != -1: - use_stdin = True + if test['command'].find("-f ") != -1: + test['command'] = '$ledger ' + test['command'] + if re.search("-f (-|/dev/stdin)(\s|$)", test['command']): + use_stdin = True else: test['command'] = (('$ledger -f "%s" ' % os.path.abspath(self.filename)) + diff --git a/test/baseline/cmd-accounts.test b/test/baseline/cmd-accounts.test index be6365fd..2f0310da 100644 --- a/test/baseline/cmd-accounts.test +++ b/test/baseline/cmd-accounts.test @@ -15,12 +15,12 @@ Assets:Testing123ÕßDone test accounts +Assets:AAA Assets:Bank -Equity:Opening balance +Assets:Testing123ÕßDone Assets:XXX -Assets:AAA Assets:♚ -Assets:Testing123ÕßDone +Equity:Opening balance end test test accounts assets:a diff --git a/test/baseline/cmd-commodities.test b/test/baseline/cmd-commodities.test index 0ce6f7a0..719b6798 100644 --- a/test/baseline/cmd-commodities.test +++ b/test/baseline/cmd-commodities.test @@ -15,10 +15,10 @@ Income:Rewards test commodities -GBP -AAA "AA2" "M&M" +AAA +GBP end test test commodities Assets:Rewards diff --git a/test/baseline/cmd-convert.test b/test/baseline/cmd-convert.test index d3fbeae5..d444da52 100644 --- a/test/baseline/cmd-convert.test +++ b/test/baseline/cmd-convert.test @@ -17,7 +17,7 @@ end test test -f /dev/null --input-date-format "%m/%d/%Y" convert test/baseline/cmd-convert3.dat -> 1 __ERROR__ -While parsing file "test/baseline/cmd-convert3.dat", line 1: +While parsing file "$sourcepath/test/baseline/cmd-convert3.dat", line 1: While parsing CSV line: 01/01/2011,, @@ -26,7 +26,7 @@ end test test -f /dev/null convert test/baseline/cmd-convert4.dat -> 1 __ERROR__ -While parsing file "test/baseline/cmd-convert4.dat", line 1: +While parsing file "$sourcepath/test/baseline/cmd-convert4.dat", line 1: While parsing CSV line: bogus,$10, diff --git a/test/baseline/feat-convert-with-diretives.dat b/test/baseline/feat-convert-with-diretives.dat new file mode 100644 index 00000000..ac13ff81 --- /dev/null +++ b/test/baseline/feat-convert-with-diretives.dat @@ -0,0 +1,3 @@ +date,payee,amount +2012/01/01,KFC,$10 +2012/01/02,"REWE SAGT DANKE 123454321",10€ diff --git a/test/baseline/feat-convert-with-diretives.test b/test/baseline/feat-convert-with-diretives.test new file mode 100644 index 00000000..2f6e0102 --- /dev/null +++ b/test/baseline/feat-convert-with-diretives.test @@ -0,0 +1,28 @@ +account Expenses:Food + payee KFC + payee REWE + +payee REWE + alias REWE SAGT DANKE + +# When reading csv file without directives: +test -f /dev/null convert test/baseline/feat-convert-with-diretives.dat +2012/01/01 * KFC + Expenses:Unknown $10 + Equity:Unknown + +2012/01/02 * REWE SAGT DANKE 123454321 + Expenses:Unknown 10€ + Equity:Unknown +end test + +# When reading csv file with directives: +test --account "Assets:Cash" convert test/baseline/feat-convert-with-diretives.dat +2012/01/01 * KFC + Expenses:Food $10 + Assets:Cash + +2012/01/02 * REWE + Expenses:Food 10€ + Assets:Cash +end test diff --git a/test/baseline/opt-aux-date.test b/test/baseline/opt-aux-date.test index 9d1e73d0..495bb7e6 100644 --- a/test/baseline/opt-aux-date.test +++ b/test/baseline/opt-aux-date.test @@ -10,7 +10,7 @@ Expenses:Books $20.00 Assets:Cash -test reg --effective +test reg --aux-date 08-Jan-01 January Expenses:Books $10.00 $10.00 Assets:Cash $-10.00 0 08-Feb-01 End of January Expenses:Books $10.00 $10.00 diff --git a/test/baseline/opt-count.test b/test/baseline/opt-count.test index 9c5495c8..7c935c7a 100644 --- a/test/baseline/opt-count.test +++ b/test/baseline/opt-count.test @@ -17,14 +17,14 @@ Assets:Cash -30.00 EUR test accounts --count -2 Expenses:Phone 4 Assets:Cash +2 Expenses:Phone 2 Expenses:Rent end test test commodities --count -4 GBP 4 EUR +4 GBP end test test payees --count @@ -33,8 +33,8 @@ test payees --count end test test commodities :rent --count -1 GBP 1 EUR +1 GBP end test test payees tag bnb --count diff --git a/test/baseline/opt-file.test b/test/baseline/opt-file.test index e01d929d..66d0ab1b 100644 --- a/test/baseline/opt-file.test +++ b/test/baseline/opt-file.test @@ -1,6 +1,6 @@ test -f opt-file-does-not-exist.dat bal -> 1 __ERROR__ -Error: Cannot read journal file "opt-file-does-not-exist.dat" +Error: Cannot read journal file "$sourcepath/opt-file-does-not-exist.dat" end test test -f test/baseline/opt-file1.dat -f test/baseline/opt-file2.dat bal diff --git a/test/garbage-input.dat b/test/garbage-input.dat new file mode 100644 index 00000000..1b7d2101 --- /dev/null +++ b/test/garbage-input.dat @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup math Mathematical objects + */ + +/** + * @file amount.h + * @author John Wiegley + * + * @ingroup math + * + * @brief Basic type for handling commoditized math: amount_t + * + * An amount is the most basic numerical type in Ledger, and relies on + * commodity.h to represent commoditized amounts, which allows Ledger to + * handle mathematical expressions involving disparate commodities. + * + * Amounts can be of virtually infinite size and precision. When + * division or multiplication is performed, the precision is + * automatically expanded to include as many extra digits as necessary + * to avoid losing information. + */ +#ifndef _AMOUNT_H +#define _AMOUNT_H + +#include "utils.h" +#include "times.h" +#include "flags.h" + +namespace ledger { + +class commodity_t; +struct annotation_t; +struct keep_details_t; + +DECLARE_EXCEPTION(amount_error, std::runtime_error); + +enum parse_flags_enum_t { + PARSE_DEFAULT = 0x00, + PARSE_PARTIAL = 0x01, + PARSE_SINGLE = 0x02, + PARSE_NO_MIGRATE = 0x04, + PARSE_NO_REDUCE = 0x08, + PARSE_NO_ASSIGN = 0x10, + PARSE_NO_ANNOT = 0x20, + PARSE_OP_CONTEXT = 0x40, + PARSE_SOFT_FAIL = 0x80 +}; + +typedef basic_flags_t<parse_flags_enum_t, uint_least8_t> parse_flags_t; + +/** + * @brief Encapsulate infinite-precision commoditized amounts + * + * Used to represent commoditized infinite-precision numbers, and + * uncommoditized, plain numbers. In the commoditized case, commodities + * keep track of how they are used, and are always displayed back to the + * user after the same fashion. For uncommoditized numbers, no display + * truncation is ever done. In both cases, internal precision is always + * kept to an excessive degree. + */ +class amount_t + : public ordered_field_operators<amount_t, + ordered_field_operators<amount_t, double, + ordered_field_operators<amount_t, unsigned long, + ordered_field_operators<amount_t, long> > > > +{ +public: + /** Ready the amount subsystem for use. + @note Normally called by session_t::initialize(). */ + static void initialize(); + /** Shutdown the amount subsystem and free all resources. + @note Normally called by session_t::shutdown(). */ + static void shutdown(); + + static bool is_initialized; + + /** The amount's decimal precision. */ + typedef uint_least16_t precision_t; + + /** Number of places of precision by which values are extended to + avoid losing precision during division and multiplication. */ + static const std::size_t extend_by_digits = 6U; + + /** If amounts should be streamed using to_fullstring() rather than + to_string(), so that complete precision is always displayed no matter + what the precision of an individual commodity may be. */ + static bool stream_fullstrings; + +protected: + void _copy(const amount_t& amt); + void _dup(); + void _clear(); + void _release(); + + struct bigint_t; + + bigint_t * quantity; + commodity_t * commodity_; + +public: + /** @name Constructors + @{ */ + + /** Creates a value for which is_null() is true, and which has no + value or commodity. If used in a value expression it evaluates to + zero, and its commodity equals \c commodity_t::null_commodity. */ + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR(amount_t, ""); + } + + /** Convert a double to an amount. As much precision as possible is + decoded from the binary floating point number. */ + amount_t(const double val); + + /** Convert an unsigned long to an amount. It's precision is zero. */ + amount_t(const unsigned long val); + + /** Convert a long to an amount. It's precision is zero, and the sign + is preserved. */ + amount_t(const long val); + + /** Parse a string as an (optionally commoditized) amount. If no + commodity is present, the resulting commodity is \c + commodity_t::null_commodity. The number may be of infinite + precision. */ + explicit amount_t(const string& val) : quantity(NULL) { + parse(val); + TRACE_CTOR(amount_t, "const string&"); + } + /** Parse a pointer to a C string as an (optionally commoditized) + amount. If no commodity is present, the resulting commodity is \c + commodity_t::null_commodity. The number may be of infinite + precision. */ + explicit amount_t(const char * val) : quantity(NULL) { + assert(val); + parse(val); + TRACE_CTOR(amount_t, "const char *"); + } + + /*@}*/ + + /** Create an amount whose display precision is never truncated, even + if the amount uses a commodity (which normally causes "round on + streaming" to occur). This function is mostly used by debugging + code and unit tests. This is the proper way to specify \c + $100.005, where display of the extra digit precision is required. + If a regular constructor were used, the amount would stream as \c + $100.01, even though its internal value equals \c $100.005. */ + static amount_t exact(const string& value); + + /** Release the reference count held for the underlying \c + amount_t::bigint_t object. */ + ~amount_t() { + TRACE_DTOR(amount_t); + if (quantity) + _release(); + } + + /** @name Assignment and copy + @{*/ + + /** Copy an amount object. Copies are very efficient, using a + copy-on-write model. Until the copy is changed, it refers to the + same memory used by the original via reference counting. The \c + amount_t::bigint_t class in amount.cc maintains the reference. */ + amount_t(const amount_t& amt) : quantity(NULL) { + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + TRACE_CTOR(amount_t, "copy"); + } + /** Copy an amount object, applying the given commodity annotation + details afterward. This is equivalent to doing a normal copy + (@see amount_t(const amount_t&)) and then calling + amount_t::annotate(). */ + amount_t(const amount_t& amt, const annotation_t& details) : quantity(NULL) { + assert(amt.quantity); + _copy(amt); + annotate(details); + TRACE_CTOR(amount_t, "const amount_t&, const annotation_t&"); + } + /** Assign an amount object. This is like copying if the amount was + null beforehand, otherwise the previous value's reference is must + be freed. */ + amount_t& operator=(const amount_t& amt); + + amount_t& operator=(const double val) { + return *this = amount_t(val); + } + amount_t& operator=(const unsigned long val) { + return *this = amount_t(val); + } + amount_t& operator=(const long val) { + return *this = amount_t(val); + } + + /* Assign a string to an amount. This causes the contents of the + string to be parsed, look for a commoditized or uncommoditized + amount specifier. */ + amount_t& operator=(const string& str) { + return *this = amount_t(str); + } + amount_t& operator=(const char * str) { + assert(str); + return *this = amount_t(str); + } + + /*@}*/ + + /** @name Comparison + @{ */ + + /** Compare two amounts, returning a number less than zero if \p amt + is greater, exactly zero if they are equal, and greater than zero + if \p amt is less. This method is used to implement all of the + other comparison methods.*/ + int compare(const amount_t& amt) const; + + /** Test two amounts for equality. First the commodity pointers are + quickly tested, then the multi-precision values themselves must be + compared. */ + bool operator==(const amount_t& amt) const; + + template <typename T> + bool operator==(const T& val) const { + return compare(val) == 0; + } + template <typename T> + bool operator<(const T& amt) const { + return compare(amt) < 0; + } + template <typename T> + bool operator>(const T& amt) const { + return compare(amt) > 0; + } + + /*@}*/ + + /** @name Binary arithmetic + */ + /*@{*/ + + amount_t& operator+=(const amount_t& amt); + amount_t& operator-=(const amount_t& amt); + amount_t& operator*=(const amount_t& amt) { + return multiply(amt); + } + amount_t& multiply(const amount_t& amt, bool ignore_commodity = false); + + /** Divide two amounts while extending the precision to preserve the + accuracy of the result. For example, if \c 10 is divided by \c 3, + the result ends up having a precision of \link + amount_t::extend_by_digits \endlink place to avoid losing internal + resolution. */ + amount_t& operator/=(const amount_t& amt); + + /*@}*/ + + /** @name Unary arithmetic + @{ */ + + /** Return an amount's internal precision. To find the precision it + should be displayed at -- assuming it was not created using + amount_t::exact() -- use the following expression instead: + @code + amount.commodity().precision() + @endcode */ + precision_t precision() const; + bool keep_precision() const; + void set_keep_precision(const bool keep = true) const; + precision_t display_precision() const; + + /** Returns the negated value of an amount. + @see operator-() + */ + amount_t negated() const { + amount_t temp(*this); + temp.in_place_negate(); + return temp; + } + void in_place_negate(); + + amount_t operator-() const { + return negated(); + } + + /** Returns the absolute value of an amount. Equivalent to: + @code + (x < * 0) ? - x : x + @endcode + */ + amount_t abs() const { + if (sign() < 0) + return negated(); + return *this; + } + + amount_t inverted() const { + amount_t temp(*this); + temp.in_place_invert(); + return temp; + } + void in_place_invert(); + + /** Yields an amount whose display precision when output is truncated + to the display precision of its commodity. This is normally the + default state of an amount, but if one has become unrounded, this + sets the "keep precision" state back to false. + @see set_keep_precision */ + amount_t rounded() const { + amount_t temp(*this); + temp.in_place_round(); + return temp; + } + void in_place_round(); + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t truncated() const { + amount_t temp(*this); + temp.in_place_truncate(); + return temp; + } + void in_place_truncate(); + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t floored() const { + amount_t temp(*this); + temp.in_place_floor(); + return temp; + } + void in_place_floor(); + + /** Yields an amount which has lost all of its extra precision, beyond what + the display precision of the commodity would have printed. */ + amount_t ceilinged() const { + amount_t temp(*this); + temp.in_place_ceiling(); + return temp; + } + void in_place_ceiling(); + + /** Yields an amount whose display precision is never truncated, even + though its commodity normally displays only rounded values. */ + amount_t unrounded() const { + amount_t temp(*this); + temp.in_place_unround(); + return temp; + } + void in_place_unround(); + + /** reduces a value to its most basic commodity form, for amounts that + utilize "scaling commodities". For example, an amount of \c 1h + after reduction will be \c 3600s. + */ + amount_t reduced() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + void in_place_reduce(); + + /** unreduce(), if used with a "scaling commodity", yields the most + compact form greater than one. That is, \c 3599s will unreduce to + \c 59.98m, while \c 3601 unreduces to \c 1h. + */ + amount_t unreduced() const { + amount_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + void in_place_unreduce(); + + /** Returns the historical value for an amount -- the default moment + returns the most recently known price -- based on the price history + for the given commodity (or determined automatically, if none is + provided). For example, if the amount were <tt>10 AAPL</tt>, and + on Apr 10, 2000 each share of \c AAPL was worth \c $10, then + calling value() for that moment in time would yield the amount \c + $100.00. + */ + optional<amount_t> + value(const datetime_t& moment = datetime_t(), + const commodity_t * in_terms_of = NULL) const; + + optional<amount_t> price() const; + + /*@}*/ + + /** @name Truth tests + */ + /*@{*/ + + /** Truth tests. An amount may be truth test in several ways: + + sign() returns an integer less than, greater than, or equal to + zero depending on whether the amount is negative, zero, or + greater than zero. Note that this function tests the actual + value of the amount -- using its internal precision -- and not + the display value. To test its display value, use: + `round().sign()'. + + is_nonzero(), or operator bool, returns true if an amount's + display value is not zero. + + is_zero() returns true if an amount's display value is zero. + Thus, $0.0001 is considered zero if the current display precision + for dollars is two decimal places. + + is_realzero() returns true if an amount's actual value is zero. + Thus, $0.0001 is never considered realzero. + + is_null() returns true if an amount has no value and no + commodity. This only occurs if an uninitialized amount has never + been assigned a value. + */ + int sign() const; + + operator bool() const { + return is_nonzero(); + } + bool is_nonzero() const { + return ! is_zero(); + } + + bool is_zero() const; + bool is_realzero() const { + return sign() == 0; + } + + bool is_null() const { + if (! quantity) { + assert(! commodity_); + return true; + } + return false; + } + + /*@}*/ + + /** @name Conversion + */ + /*@{*/ + + /** Conversion methods. An amount may be converted to the same types + it can be constructed from -- with the exception of unsigned + long. Implicit conversions are not allowed in C++ (though they + are in Python), rather the following conversion methods must be + called explicitly: + + to_double([bool]) returns an amount as a double. If the optional + boolean argument is true (the default), an exception is thrown if + the conversion would lose information. + + to_long([bool]) returns an amount as a long integer. If the + optional boolean argument is true (the default), an exception is + thrown if the conversion would lose information. + + fits_in_long() returns true if to_long() would not lose + precision. + + to_string() returns an amount'ss "display value" as a string -- + after rounding the value according to the commodity's default + precision. It is equivalent to: `round().to_fullstring()'. + + to_fullstring() returns an amount's "internal value" as a string, + without any rounding. + + quantity_string() returns an amount's "display value", but + without any commodity. Note that this is different from + `number().to_string()', because in that case the commodity has + been stripped and the full, internal precision of the amount + would be displayed. + */ + double to_double() const; + long to_long() const; + bool fits_in_long() const; + + operator string() const { + return to_string(); + } + string to_string() const; + string to_fullstring() const; + string quantity_string() const; + + /*@}*/ + + /** @name Commodity methods + */ + /*@{*/ + + /** The following methods relate to an + amount's commodity: + + commodity() returns an amount's commodity. If the amount has no + commodity, the value returned is the `null_commodity'. + + has_commodity() returns true if the amount has a commodity. + + set_commodity(commodity_t) sets an amount's commodity to the + given value. Note that this merely sets the current amount to + that commodity, it does not "observe" the amount for possible + changes in the maximum display precision of the commodity, the + way that `parse' does. + + clear_commodity() sets an amount's commodity to null, such that + has_commodity() afterwards returns false. + + number() returns a commodity-less version of an amount. This is + useful for accessing just the numeric portion of an amount. + */ + commodity_t * commodity_ptr() const; + commodity_t& commodity() const { + return *commodity_ptr(); + } + + bool has_commodity() const; + void set_commodity(commodity_t& comm) { + if (! quantity) + *this = 0L; + commodity_ = &comm; + } + amount_t with_commodity(const commodity_t& comm) const { + if (commodity_ == &comm) { + return *this; + } else { + amount_t tmp(*this); + tmp.set_commodity(const_cast<commodity_t&>(comm)); + return tmp; + } + } + void clear_commodity() { + commodity_ = NULL; + } + + amount_t number() const { + if (! has_commodity()) + return *this; + + amount_t temp(*this); + temp.clear_commodity(); + return temp; + } + + /*@}*/ + + /** @name Commodity annotations + */ + /*@{*/ + + /** An amount's commodity may be annotated with special details, such as the + price it was purchased for, when it was acquired, or an arbitrary note, + identifying perhaps the lot number of an item. + + annotate_commodity(amount_t price, [datetime_t date, string tag]) + sets the annotations for the current amount's commodity. Only + the price argument is required, although it can be passed as + `none' if no price is desired. + + commodity_annotated() returns true if an amount's commodity has + any annotation details associated with it. + + annotation_details() returns all of the details of an annotated + commodity's annotations. The structure returns will evaluate as + boolean false if there are no details. + + strip_annotations() returns an amount whose commodity's annotations have + been stripped. + */ + void annotate(const annotation_t& details); + bool has_annotation() const; + + annotation_t& annotation(); + const annotation_t& annotation() const { + return const_cast<amount_t&>(*this).annotation(); + } + + /** If the lot price is considered whenever working with commoditized + values. + + Let's say a user adds two values of the following form: + @code + 10 AAPL + 10 AAPL {$20} + @endcode + + This expression adds ten shares of Apple stock with another ten + shares that were purchased for \c $20 a share. If \c keep_price + is false, the result of this expression is an amount equal to + <tt>20 AAPL</tt>. If \c keep_price is \c true the expression + yields an exception for adding amounts with different commodities. + In that case, a \link balance_t \endlink object must be used to + store the combined sum. */ + amount_t strip_annotations(const keep_details_t& what_to_keep) const; + + /*@}*/ + + /** @name Parsing + */ + /*@{*/ + + /** The `flags' argument of both parsing may be one or more of the + following: + + PARSE_NO_MIGRATE means to not pay attention to the way an + amount is used. Ordinarily, if an amount were $100.001, for + example, it would cause the default display precision for $ to be + "widened" to three decimal places. If PARSE_NO_MIGRATE is + used, the commodity's default display precision is not changed. + + PARSE_NO_REDUCE means not to call in_place_reduce() on the + resulting amount after it is parsed. + + These parsing methods observe the amounts they parse (unless + PARSE_NO_MIGRATE is true), and set the display details of + the corresponding commodity accordingly. This way, amounts do + not require commodities to be pre-defined in any way, but merely + displays them back to the user in the same fashion as it saw them + used. + + There is also a static convenience method called + `parse_conversion' which can be used to define a relationship + between scaling commodity values. For example, Ledger uses it to + define the relationships among various time values: + + @code + amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds + amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes + @endcode + + The method parse() is used to parse an amount from an input stream + or a string. A global operator>>() is also defined which simply + calls parse on the input stream. The parse() method has two forms: + + parse(istream, flags_t) parses an amount from the given input + stream. + + parse(string, flags_t) parses an amount from the given string. + + parse(string, flags_t) also parses an amount from a string. + */ + bool parse(std::istream& in, + const parse_flags_t& flags = PARSE_DEFAULT); + bool parse(const string& str, + const parse_flags_t& flags = PARSE_DEFAULT) { + std::istringstream stream(str); + bool result = parse(stream, flags); + return result; + } + + static void parse_conversion(const string& larger_str, + const string& smaller_str); + + /*@}*/ + + /** @name Printing + */ + /*@{*/ + + /** An amount may be output to a stream using the `print' method. There is + also a global operator<< defined which simply calls print for an amount + on the given stream. There is one form of the print method, which takes + one required argument and two arguments with default values: + + print(ostream, bool omit_commodity = false, bool full_precision = false) + prints an amounts to the given output stream, using its commodity's + default display characteristics. If `omit_commodity' is true, the + commodity will not be displayed, only the amount (although the + commodity's display precision is still used). If `full_precision' is + true, the full internal precision of the amount is displayed, regardless + of its commodity's display precision. + */ +#define AMOUNT_PRINT_NO_FLAGS 0x00 +#define AMOUNT_PRINT_RIGHT_JUSTIFY 0x01 +#define AMOUNT_PRINT_COLORIZE 0x02 +#define AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS 0x04 +#define AMOUNT_PRINT_ELIDE_COMMODITY_QUOTES 0x08 + + void print(std::ostream& out, + const uint_least8_t flags = AMOUNT_PRINT_NO_FLAGS) const; + + /*@}*/ + + /** @name Debugging + */ + /*@{*/ + + /** There are two methods defined to help with debugging: + + dump(ostream) dumps an amount to an output stream. There is + little different from print(), it simply surrounds the display + value with a marker, for example "AMOUNT($1.00)". This code is + used by other dumping code elsewhere in Ledger. + + valid() returns true if an amount is valid. This ensures that if + an amount has a commodity, it has a valid value pointer, for + example, even if that pointer simply points to a zero value. + */ + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + + bool valid() const; + +#if HAVE_BOOST_SERIALIZATION +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /* version */); +#endif // HAVE_BOOST_SERIALIZATION + + /*@}*/ +}; + +inline amount_t amount_t::exact(const string& value) { + amount_t temp; + temp.parse(value, 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; + unrounded().print(bufstream); + return bufstream.str(); +} + +inline string amount_t::quantity_string() const { + std::ostringstream bufstream; + number().print(bufstream); + return bufstream.str(); +} + +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + if (amount_t::stream_fullstrings) + amt.unrounded().print(out); + else + amt.print(out); + return out; +} +inline std::istream& operator>>(std::istream& in, amount_t& amt) { + amt.parse(in); + return in; +} + +void put_amount(property_tree::ptree& pt, const amount_t& amt, + bool wrap = true, bool commodity_details = false); + +} // namespace ledger + +#endif // _AMOUNT_H diff --git a/test/input/demo.ledger b/test/input/demo.ledger new file mode 100644 index 00000000..dbf47c2a --- /dev/null +++ b/test/input/demo.ledger @@ -0,0 +1,63 @@ +2010/12/01 * Checking balance + Assets:Checking $1,000.00 + Equity:Opening Balances + +2010/12/20 * Organic Co-op + Expenses:Food:Groceries $ 37.50 ; [=2011/01/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/02/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/03/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/04/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/05/01] + Expenses:Food:Groceries $ 37.50 ; [=2011/06/01] + Assets:Checking $ -225.00 + +2010/12/28 Acme Mortgage + Liabilities:Mortgage:Principal $ 200.00 + Expenses:Interest:Mortgage $ 500.00 + Expenses:Escrow $ 300.00 + * Assets:Checking $ -1000.00 + +2011/01/02 Grocery Store + Expenses:Food:Groceries $ 65.00 + * Assets:Checking + +2011/01/05 Employer + * Assets:Checking $ 2000.00 + Income:Salary + +2011/01/14 Bank + ; Regular monthly savings transfer + Assets:Savings $ 300.00 + Assets:Checking + +2011/01/19 Grocery Store + Expenses:Food:Groceries $ 44.00 ; hastag: not block + Assets:Checking + +2011/01/25 Bank + ; Transfer to cover car purchase + Assets:Checking $ 5,500.00 + Assets:Savings + ; :nobudget: + +2011/01/25 Tom's Used Cars + Expenses:Auto $ 5,500.00 + ; :nobudget: + Assets:Checking + +2011/01/27 Book Store + Expenses:Books $20.00 + Liabilities:MasterCard + +2011/04/25 Tom's Used Cars + Expenses:Auto $ 5,500.00 + ; :nobudget: + Assets:Checking + +2011/04/27 Bookstore + Expenses:Books $20.00 + Assets:Checking + +2011/12/01 Sale + Assets:Checking $ 30.00 + Income:Sales diff --git a/test/regress/0161EB1E.test b/test/regress/0161EB1E.test new file mode 100644 index 00000000..93498ad5 --- /dev/null +++ b/test/regress/0161EB1E.test @@ -0,0 +1,15 @@ +bucket Assets:Checking +2011/04/25 Tom's Used Cars + Auto $ 5,500.00 + ; :nobudget: + +A Assets:Checking +2011/04/27 Book Store + Books $20.00 + +test reg +11-Apr-25 Tom's Used Cars Auto $ 5,500.00 $ 5,500.00 + Assets:Checking $ -5,500.00 0 +11-Apr-27 Book Store Books $ 20.00 $ 20.00 + Assets:Checking $ -20.00 0 +end test diff --git a/test/regress/25A099C9.test b/test/regress/25A099C9.test index 1ef5ebef..e511c799 100644 --- a/test/regress/25A099C9.test +++ b/test/regress/25A099C9.test @@ -1,43 +1,43 @@ -test -f src/amount.h reg -> 20 +test -f test/garbage-input.dat reg -> 20 __ERROR__ -While parsing file "src/amount.h", line 2: +While parsing file "$sourcepath/test/garbage-input.dat", line 2: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 33: +While parsing file "$sourcepath/test/garbage-input.dat", line 33: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 37: +While parsing file "$sourcepath/test/garbage-input.dat", line 37: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 66: +While parsing file "$sourcepath/test/garbage-input.dat", line 66: Error: No quantity specified for amount -While parsing file "src/amount.h", line 69: +While parsing file "$sourcepath/test/garbage-input.dat", line 69: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 83: +While parsing file "$sourcepath/test/garbage-input.dat", line 83: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 93: +While parsing file "$sourcepath/test/garbage-input.dat", line 93: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 99: +While parsing file "$sourcepath/test/garbage-input.dat", line 99: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 121: +While parsing file "$sourcepath/test/garbage-input.dat", line 121: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 132: +While parsing file "$sourcepath/test/garbage-input.dat", line 132: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 702: +While parsing file "$sourcepath/test/garbage-input.dat", line 711: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 732: +While parsing file "$sourcepath/test/garbage-input.dat", line 741: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 740: +While parsing file "$sourcepath/test/garbage-input.dat", line 749: Error: Unexpected whitespace at beginning of line -While parsing file "src/amount.h", line 743: +While parsing file "$sourcepath/test/garbage-input.dat", line 752: Error: Invalid date/time: line amount_t amoun -While parsing file "src/amount.h", line 749: +While parsing file "$sourcepath/test/garbage-input.dat", line 758: Error: Invalid date/time: line string amount_ -While parsing file "src/amount.h", line 755: +While parsing file "$sourcepath/test/garbage-input.dat", line 764: Error: Invalid date/time: line string amount_ -While parsing file "src/amount.h", line 761: +While parsing file "$sourcepath/test/garbage-input.dat", line 770: Error: Invalid date/time: line string amount_ -While parsing file "src/amount.h", line 767: +While parsing file "$sourcepath/test/garbage-input.dat", line 776: Error: Invalid date/time: line std::ostream& -While parsing file "src/amount.h", line 774: +While parsing file "$sourcepath/test/garbage-input.dat", line 783: Error: Invalid date/time: line std::istream& -While parsing file "src/amount.h", line 780: +While parsing file "$sourcepath/test/garbage-input.dat", line 789: Error: Unexpected whitespace at beginning of line end test diff --git a/test/regress/BF3C1F82-2.test b/test/regress/BF3C1F82-2.test new file mode 100644 index 00000000..453151ce --- /dev/null +++ b/test/regress/BF3C1F82-2.test @@ -0,0 +1,12 @@ +; Check that include directives are relative for "-f /dev/stdin" +include non-existent-ledger-file-BF3C1F82 +test -f - reg -> 1 +__ERROR__ +While parsing file "", line 2: +Error: File to include was not found: "./non-existent-ledger-file-BF3C1F82" +end test +test -f /dev/stdin reg -> 1 +__ERROR__ +While parsing file "", line 2: +Error: File to include was not found: "./non-existent-ledger-file-BF3C1F82" +end test diff --git a/test/regress/BF3C1F82.test b/test/regress/BF3C1F82.test new file mode 100644 index 00000000..50f4106f --- /dev/null +++ b/test/regress/BF3C1F82.test @@ -0,0 +1,19 @@ +; Check that error reporting works for "-f -" + +2012/02/30 * Test + a 1 + b +test -f - reg -> 1 +__ERROR__ +While parsing file "", line 3: +While parsing transaction: +<no source context> +Error: Day of month is not valid for year +end test +test -f /dev/stdin reg -> 1 +__ERROR__ +While parsing file "", line 3: +While parsing transaction: +<no source context> +Error: Day of month is not valid for year +end test diff --git a/test/regress/CAE63F5C-a.test b/test/regress/CAE63F5C-a.test new file mode 100644 index 00000000..4465bd2f --- /dev/null +++ b/test/regress/CAE63F5C-a.test @@ -0,0 +1,17 @@ +2011/03/01 test1 + a 4.00 € + b + +2011/03/02 test2 + a 4.00 € + b + +2011/03/03 test2 + a 4.00 € + b + +test reg a +11-Mar-01 test1 a 4.00 € 4.00 € +11-Mar-02 test2 a 4.00 € 8.00 € +11-Mar-03 test2 a 4.00 € 12.00 € +end test diff --git a/test/regress/CAE63F5C-b.test b/test/regress/CAE63F5C-b.test new file mode 100644 index 00000000..c0b7efd8 --- /dev/null +++ b/test/regress/CAE63F5C-b.test @@ -0,0 +1,15 @@ +2012/08/22 Payment + Accrued €208.00 {=$1.3109} @ $1.2799 + Expenses €4.16 {=$1.2798689} @ $1.2799 + Assets $-271.54 + Income:Currency Conversion $-6.45 + +test bal -X $ + $272.67 Accrued + $-271.54 Assets + $6.45 Equity:Capital Gains + $5.32 Expenses + $-6.45 Income:Currency Conversion +-------------------- + $6.45 +end test diff --git a/test/regress/CAE63F5C-c.test b/test/regress/CAE63F5C-c.test new file mode 100644 index 00000000..ae2d7d10 --- /dev/null +++ b/test/regress/CAE63F5C-c.test @@ -0,0 +1,15 @@ +2012/08/22 Payment + Accrued €208.00 {=$1.3109} @ $1.2798689 + Expenses €4.16 {=$1.2798689} @ $1.2798689 + Assets $-271.54 + Income:Currency Conversion $-6.45 + +test bal -X $ + $272.67 Accrued + $-271.54 Assets + $6.45 Equity:Capital Gains + $5.32 Expenses + $-6.45 Income:Currency Conversion +-------------------- + $6.45 +end test diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 4b6232dd..26f55e84 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -1,19 +1 @@ -if(HAVE_BOOST_PYTHON) - set(TEST_PYTHON_FLAGS "--python") -endif() - -if(PYTHONINTERP_FOUND) - file(GLOB REGRESSION_TESTS *.test) - foreach(TestFile ${REGRESSION_TESTS}) - get_filename_component(TestFile_Name ${TestFile} NAME_WE) - string(FIND ${TestFile_Name} "_py" TestFile_IsPythonTest) - if((NOT TestFile_IsPythonTest) OR HAVE_BOOST_PYTHON) - add_test(RegressionTest_${TestFile_Name} - ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/RegressTests.py - ${LEDGER_LOCATION} ${PROJECT_SOURCE_DIR} - ${TestFile} ${TEST_PYTHON_FLAGS}) - set_target_properties(check - PROPERTIES DEPENDS RegressionTest_${TestFile_Name}) - endif() - endforeach() -endif() +add_ledger_harness_tests(Regress) diff --git a/test/regress/xact_code.dat b/test/regress/xact_code.dat new file mode 100644 index 00000000..60956a23 --- /dev/null +++ b/test/regress/xact_code.dat @@ -0,0 +1,3 @@ +2012-11-10 (C0-d3) Payee + Assets:Checking € -12,45 + Expenses:Expenditure diff --git a/test/regress/xact_code.py b/test/regress/xact_code.py new file mode 100644 index 00000000..64abb17d --- /dev/null +++ b/test/regress/xact_code.py @@ -0,0 +1,4 @@ +import ledger + +for post in ledger.read_journal('test/regress/xact_code.dat').query('expenses'): + print post.xact.code diff --git a/test/regress/xact_code_py.test b/test/regress/xact_code_py.test new file mode 100644 index 00000000..c22158e0 --- /dev/null +++ b/test/regress/xact_code_py.test @@ -0,0 +1,3 @@ +test python test/regress/xact_code.py +C0-d3 +end test diff --git a/test/unit/t_amount.cc b/test/unit/t_amount.cc index 07cde8f3..b82de510 100644 --- a/test/unit/t_amount.cc +++ b/test/unit/t_amount.cc @@ -1109,6 +1109,60 @@ BOOST_AUTO_TEST_CASE(testCommodityAbs) BOOST_CHECK(x2.valid()); } +BOOST_AUTO_TEST_CASE(testFloor) +{ + amount_t x0; + amount_t x1("123.123"); + amount_t x2("-123.123"); + + BOOST_CHECK_THROW(x0.floored(), amount_error); + BOOST_CHECK_EQUAL(amount_t(123L), x1.floored()); + BOOST_CHECK_EQUAL(amount_t(-124L), x2.floored()); + + BOOST_CHECK(x0.valid()); + BOOST_CHECK(x1.valid()); + BOOST_CHECK(x2.valid()); +} + +BOOST_AUTO_TEST_CASE(testCommodityFloor) +{ + amount_t x1("$1234.56"); + amount_t x2("$-1234.56"); + + BOOST_CHECK_EQUAL(amount_t("$1234"), x1.floored()); + BOOST_CHECK_EQUAL(amount_t("$-1235"), x2.floored()); + + BOOST_CHECK(x1.valid()); + BOOST_CHECK(x2.valid()); +} + +BOOST_AUTO_TEST_CASE(testCeiling) +{ + amount_t x0; + amount_t x1("123.123"); + amount_t x2("-123.123"); + + BOOST_CHECK_THROW(x0.ceilinged(), amount_error); + BOOST_CHECK_EQUAL(amount_t(124L), x1.ceilinged()); + BOOST_CHECK_EQUAL(amount_t(-123L), x2.ceilinged()); + + BOOST_CHECK(x0.valid()); + BOOST_CHECK(x1.valid()); + BOOST_CHECK(x2.valid()); +} + +BOOST_AUTO_TEST_CASE(testCommodityCeiling) +{ + amount_t x1("$1234.56"); + amount_t x2("$-1234.56"); + + BOOST_CHECK_EQUAL(amount_t("$1235"), x1.ceilinged()); + BOOST_CHECK_EQUAL(amount_t("$-1234"), x2.ceilinged()); + + BOOST_CHECK(x1.valid()); + BOOST_CHECK(x2.valid()); +} + #ifndef NOT_FOR_PYTHON #if 0 BOOST_AUTO_TEST_CASE(testReduction) diff --git a/tools/build.sh b/tools/build.sh index 185fe791..ba563e34 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -3,10 +3,14 @@ flavor=$1 shift 1 -time ( \ - cd ~/src/ledger ; \ - PATH=/usr/local/bin:/opt/local/bin:$PATH \ - nice -n 20 ./acprep $flavor --debug --python --doxygen make "$@" && \ - PATH=/usr/local/bin:/opt/local/bin:$PATH \ - nice -n 20 ./acprep $flavor --debug --python --doxygen check "$@" \ -)
\ No newline at end of file +JOBS=-j$(sysctl -n hw.activecpu) +OPTIONS="$flavor --debug --python --ninja --doxygen $JOBS" +OPTIONS="$OPTIONS --prefix /usr/local/Cellar/ledger/HEAD" + +time ( \ + cd ~/src/ledger ; \ + PATH=/usr/local/bin:/opt/local/bin:$PATH \ + nice -n 20 ./acprep $OPTIONS make $JOBS "$@" && \ + PATH=/usr/local/bin:/opt/local/bin:$PATH \ + nice -n 20 ./acprep $OPTIONS check $JOBS "$@" \ +) diff --git a/tools/gendocs.sh b/tools/gendocs.sh index 55933b13..9126a406 100755 --- a/tools/gendocs.sh +++ b/tools/gendocs.sh @@ -2,4 +2,4 @@ echo "===================================== Making Info..." makeinfo ledger3.texi echo "===================================== Making PDF..." -texi2pdf ledger3.texi +texi2pdf --quiet --batch ledger3.texi |