pax_global_header00006660000000000000000000000064126602344100014510gustar00rootroot0000000000000052 comment=5228ec5a9fe8869fe4fbd97fa65b091236373cda ledger-3.1.1+dfsg1/000077500000000000000000000000001266023441000137545ustar00rootroot00000000000000ledger-3.1.1+dfsg1/.dir-locals.el000066400000000000000000000020241266023441000164030ustar00rootroot00000000000000;;; Directory Local Variables ;;; For more information see (info "(emacs) Directory Variables") ((nil (tab-width . 2) (sentence-end-double-space . t) (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 . "")))) (emacs-lisp-mode (indent-tabs-mode))) ledger-3.1.1+dfsg1/.gitignore000066400000000000000000000032131266023441000157430ustar00rootroot00000000000000*.[oa] *.so *.so.* *.dylib *.backup *.elc *.gcov *.l[oa] *.pyc *.sw[p-z] *~ .timestamp *.tar.bz2 *.tar.gz .deps/ .libs/ ABOUT-NLS Makefile Makefile.am Makefile.in TAGS acconf.h.in aclocal.m4 autogen.sh autom4te.cache/ config.guess config.h config.h.in config.log config.rpath config.status config.sub configure configure.ac depcomp doc/Doxyfile doc/*.aux doc/*.cp doc/*.cps doc/*.fn doc/*.fns doc/*.html doc/*.info doc/*.info-* doc/*.ky doc/*.kys doc/*.log doc/*.pdf doc/*.pg doc/*.toc doc/*.tp doc/*.vr doc/*.vrs doc/.dirstamp doc/html/ doc/latex/ doc/refman.pdf doc/report/ doc/version.texi elisp-comp install-sh intl/ ledger libtool ltmain.sh m4/ make.sh missing mkinstalldirs po/ py-compile shave shave-libtool stamp-h1 texinfo.tex tmpcvs*/ tmpwrk*/ dist/win/vc9/Debug/ dist/win/vc9/gen-mpir.exe dist/win/vc9/gen-mpir.ilk dist/win/vc9/gen-mpir.pdb dist/win/vc9/ledger.ncb dist/win/vc9/ledger.vcproj.*.user dist/win/vc9/ledger.suo dist/win/vc9/lib/Win32/Debug/ src/TAGS CMakeCache.txt CPackConfig.cmake CPackSourceConfig.cmake CMakeFiles/ CTestTestfile.cmake _CPack_Packages/ cmake_install.cmake install_manifest.txt 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 /wiki/ .ninja_deps .ninja_log build.ninja rules.ninja test/Testing /MathTests /UtilTests ledger-3.1.1+dfsg1/.gitmodules000066400000000000000000000000001266023441000161170ustar00rootroot00000000000000ledger-3.1.1+dfsg1/.travis.yml000066400000000000000000000127061266023441000160730ustar00rootroot00000000000000# Since the Travis CI environment http://docs.travis-ci.com/user/ci-environment/ # provides GNU GCC 4.6, which does not support -std=c++11 GNU GCC 4.8 is installed # NOTE: Please validate this file after editing it using # Travis WebLint https://lint.travis-ci.org/ # or travis-lint https://github.com/travis-ci/travis-lint language: cpp compiler: - gcc - clang os: - linux - osx sudo: false cache: apt: true env: global: # Boost version to use: # _MIN is used when building the master branch # _MAX is used when building any other branch - BOOST_VERSION_MIN="1.49.0" - BOOST_VERSION_MAX="1.60.0" # List of required boost libraries to build - BOOST_LIBS="date_time,filesystem,iostreams,python,regex,system,test" # List of required Homebrew formulae to install - BREWS="gmp,mpfr" # Encrypted COVERITY_SCAN_TOKEN - secure: "mYNxD1B8WNSvUeKzInehZ7syi2g1jH2ymeSQxoeKKD2duq3pvNWPdZdc4o9MlWQcAqcz58rhFZRIpuEWCnP0LbbJaG+MyuemMn9uAmg9Y4gFpMsBPHuTdf8pO3rDex+tkrr9puEJFgL+QV/TehxO6NDDpx7UdYvJb+4aZD/auYI=" matrix: exclude: - os: linux compiler: clang # Compiling ledger on Linux with clang # either crashes clang or results in a ledger binary that crashes with SIGSEGV. - os: osx compiler: gcc # On Mac OS X building ledger with GNU GCC 4.8 fails due to # undefined symbols, maybe because boost was not being built with g++-4.8. # Undefined symbols for architecture x86_64: # "boost::re_detail::perl_matcher >, boost::regex_traits > >::construct_init(boost::basic_regex > > const&, boost::regex_constants::_match_flags)", referenced from: # boost::re_detail::perl_matcher >, boost::regex_traits > >::perl_matcher(char const*, char const*, boost::match_results > >&, boost::basic_regex > > const&, boost::regex_constants::_match_flags, char const*) in main.cc.o # boost::re_detail::perl_matcher >, boost::regex_traits > >::perl_matcher(char const*, char const*, boost::match_results > >&, boost::basic_regex > > const&, boost::regex_constants::_match_flags, char const*) in global.cc.o # "boost::re_detail::perl_matcher >, boost::regex_traits > >::find()", referenced from: # bool boost::regex_search > >(char const*, char const*, boost::basic_regex > > const&, boost::regex_constants::_match_flags) in main.cc.o # bool boost::regex_search > >(char const*, char const*, boost::basic_regex > > const&, boost::regex_constants::_match_flags) in global.cc.o addons: coverity_scan: project: name: "ledger/ledger" description: "Build submitted via Travis CI" build_command_prepend: "cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON -DCLANG_GCOV=ON" build_command: "make" branch_pattern: coverity apt: sources: - ubuntu-toolchain-r-test #- boost-latest packages: - gcc-4.8 - g++-4.8 - libgmp-dev - libmpfr-dev - libedit-dev #- libboost1.55-dev #- libboost-test1.55-dev #- libboost-regex1.55-dev #- libboost-python1.55-dev #- libboost-system1.55-dev #- libboost-date-time1.55-dev #- libboost-iostreams1.55-dev #- libboost-filesystem1.55-dev #- libboost-serialization1.55-dev before_install: - if [ "${TRAVIS_BRANCH}" = "master" ]; then export BOOST_VERSION="${BOOST_VERSION_MIN}"; else export BOOST_VERSION="${BOOST_VERSION_MAX}"; fi - if [ -n "${BOOST_VERSION}" ]; then export BOOST_ROOT="${TRAVIS_BUILD_DIR}/../boost-trunk"; export CMAKE_MODULE_PATH="${BOOST_ROOT}"; fi - if [ "${CXX}" = "g++" ]; then export CXX="$(which g++-4.8)"; export CC="$(which gcc-4.8)"; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then export DYLD_LIBRARY_PATH="${BOOST_ROOT}/lib"; fi # c++ is a symlink to clang++, but the compiler behaves differently when invoked as c++ - if [ "${TRAVIS_OS_NAME}" = "osx" -a "${CXX}" = "clang++" ]; then export CXX="$(which c++)"; export CC="$(which cc)"; fi - tools/travis-before_install.sh install: - tools/travis-install.sh before_script: - cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON - make script: - ctest --output-on-failure - PYTHONPATH=. python python/demo.py after_script: # These scripts are run for informational purposes and # should be reintegrated into CTest once they reliably verify the documentation. - python test/CheckTexinfo.py -l ledger -s . - python test/CheckManpage.py -l ledger -s . notifications: email: on_success: change on_failure: change irc: channels: [ "chat.freenode.net#ledger" ] on_success: change on_failure: change ledger-3.1.1+dfsg1/CMakeLists.txt000066400000000000000000000222251266023441000165170ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.5) if(POLICY CMP0042) # CMP0042 is only known to CMake 3.0 and above cmake_policy(SET CMP0042 OLD) endif(POLICY CMP0042) PROJECT(ledger) set(Ledger_VERSION_MAJOR 3) set(Ledger_VERSION_MINOR 1) set(Ledger_VERSION_PATCH 1) set(Ledger_VERSION_PRERELEASE "") set(Ledger_VERSION_DATE 20160111) set(Ledger_TEST_TIMEZONE "America/Chicago") # Point CMake at any custom modules we may ship list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") enable_testing() add_definitions(-std=c++11) if (CYGWIN) add_definitions(-U__STRICT_ANSI__) endif() ######################################################################## option(USE_PYTHON "Build support for the Python scripting bridge" OFF) option(USE_DOXYGEN "Build reference documentation using Doxygen" OFF) 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_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) set(CMAKE_BUILD_TYPE Debug) set(DEBUG_MODE 1) else() set(CMAKE_BUILD_TYPE Release) set(DEBUG_MODE 0) endif() if (DISABLE_ASSERTS) set(NO_ASSERTS 1) else() set(NO_ASSERTS 0) endif() if (CLANG_GCOV) set(PROFILE_LIBS profile_rt) set(CMAKE_REQUIRED_LIBRARIES ${PROFILE_LIBS}) endif() ######################################################################## set(Python_ADDITIONAL_VERSIONS 2.7 2.6) find_package(PythonInterp) # Used for running tests if (USE_PYTHON) if (NOT BUILD_LIBRARY) message(ERROR "Building the python module requires BUILD_LIBRARY=ON.") endif() find_package(PythonLibs) if (PYTHONLIBS_FOUND) set(BOOST_PYTHON python) set(HAVE_BOOST_PYTHON 1) include_directories(SYSTEM ${PYTHON_INCLUDE_DIRS}) else() set(HAVE_BOOST_PYTHON 0) message("Could not find a Python library to use with Boost.Python") endif() else() set(HAVE_BOOST_PYTHON 0) endif() # Set BOOST_ROOT to help CMake to find the right Boost version find_package(Boost 1.49.0 REQUIRED date_time filesystem system iostreams regex unit_test_framework ${BOOST_PYTHON}) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) link_directories(${Boost_LIBRARY_DIRS}) ######################################################################## include(CheckIncludeFiles) include(CheckLibraryExists) include(CheckFunctionExists) include(CheckCSourceCompiles) include(CheckCXXSourceRuns) include(CMakePushCheckState) check_function_exists(access HAVE_ACCESS) check_function_exists(realpath HAVE_REALPATH) check_function_exists(getpwuid HAVE_GETPWUID) check_function_exists(getpwnam HAVE_GETPWNAM) check_function_exists(ioctl HAVE_IOCTL) check_function_exists(isatty HAVE_ISATTY) check_c_source_compiles(" #include #include #include #include #include #include int main() { int status, pfd[2]; status = pipe(pfd); status = fork(); if (status < 0) { ; } else if (status == 0) { char *arg0 = NULL; status = dup2(pfd[0], STDIN_FILENO); close(pfd[1]); close(pfd[0]); execlp(\"\", arg0, (char *)0); perror(\"execl\"); exit(1); } else { close(pfd[0]); } return 0; }" UNIX_PIPES_COMPILES) if (UNIX_PIPES_COMPILES) set(HAVE_UNIX_PIPES 1) else() set(HAVE_UNIX_PIPES 0) endif() cmake_push_check_state() set(CMAKE_REQUIRED_INCLUDES ${CMAKE_INCLUDE_PATH} ${Boost_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Boost_LIBRARIES} icuuc ${PROFILE_LIBS}) check_cxx_source_runs(" #include using namespace boost; int main() { std::string text = \"Активы\"; u32regex r = make_u32regex(\"активы\", regex::perl | regex::icase); return u32regex_search(text, r) ? 0 : 1; }" BOOST_REGEX_UNICODE_RUNS) if (BOOST_REGEX_UNICODE_RUNS) set(HAVE_BOOST_REGEX_UNICODE 1) else() set(HAVE_BOOST_REGEX_UNICODE 0) endif() cmake_pop_check_state() # Check if fix for https://github.com/boostorg/python/issues/39 is needed if (HAVE_BOOST_PYTHON) cmake_push_check_state() set(CMAKE_REQUIRED_INCLUDES ${CMAKE_INCLUDE_PATH} ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ${PROFILE_LIBS}) check_cxx_source_runs(" #include struct X { int y; }; int main() { boost::python::make_setter(&X::y); }" BOOST_MAKE_SETTER_RUNS) if (BOOST_MAKE_SETTER_RUNS) set(HAVE_BOOST_159_ISSUE_39 0) else() set(HAVE_BOOST_159_ISSUE_39 1) endif() cmake_pop_check_state() endif() ######################################################################## include_directories(${CMAKE_INCLUDE_PATH}) macro(find_opt_library_and_header _header_var _header _lib_var _lib _have_var) if (${_have_var}) find_path(${_header_var} ${_header}) if (NOT ${_header_var}) set(${_have_var} 0) else() find_library(${_lib_var} ${_lib}) if (NOT ${_lib_var}) set(${_have_var} 0) else() include_directories(SYSTEM "${${_header_var}}") set(${_have_var} 1) endif() endif() else() set(${_have_var} 0) endif() endmacro(find_opt_library_and_header _header_var _header _lib_var _lib _have_var) macro(find_req_library_and_header _header_var _header _lib_var _lib) find_path(${_header_var} ${_header}) if (NOT ${_header_var}) message(SEND_ERROR "Could not find ${_header} on your system") else() include_directories(SYSTEM "${${_header_var}}") find_library(${_lib_var} ${_lib}) if (NOT ${_lib_var}) message(SEND_ERROR "Could not find library ${_lib} on your system") endif() endif() endmacro(find_req_library_and_header _header_var _header _lib_var _lib) find_req_library_and_header(GMP_PATH gmp.h GMP_LIB gmp) find_req_library_and_header(MPFR_PATH mpfr.h MPFR_LIB mpfr) check_library_exists(edit readline "" HAVE_EDIT) find_opt_library_and_header(EDIT_PATH histedit.h EDIT_LIB edit HAVE_EDIT) #find_package(Gettext) # Used for running tests #if (GETTEXT_FOUND) # set(HAVE_GETTEXT 1) #else() set(HAVE_GETTEXT 0) #endif() #find_path(INTL_PATH libintl.h) #find_library(INTL_LIB intl) #include_directories(SYSTEM "${INTL_PATH}") ######################################################################## macro(add_ledger_library_dependencies _target) target_link_libraries(${_target} ${MPFR_LIB}) target_link_libraries(${_target} ${GMP_LIB}) if (HAVE_EDIT) target_link_libraries(${_target} ${EDIT_LIB}) endif() if (HAVE_GETTEXT) target_link_libraries(${_target} ${INTL_LIB}) endif() if (HAVE_BOOST_PYTHON) if(CMAKE_SYSTEM_NAME STREQUAL Darwin) # Don't link directly to a Python framework on OS X, to avoid segfaults # when the module is imported from a different interpreter target_link_libraries(${_target} ${Boost_LIBRARIES}) set_target_properties(${_target} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") else() target_link_libraries(${_target} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) endif() else() target_link_libraries(${_target} ${Boost_LIBRARIES}) endif() if (HAVE_BOOST_REGEX_UNICODE) target_link_libraries(${_target} icuuc) endif() target_link_libraries(${_target} ${PROFILE_LIBS}) endmacro(add_ledger_library_dependencies _target) ######################################################################## include(FindUtfcpp) if (UTFCPP_FOUND) include_directories("${UTFCPP_INCLUDE_DIR}") else() message(FATAL_ERROR "Missing required header file: utf8.h\n" "Define UTFCPP_PATH or install utfcpp locally into the source tree below lib/utfcpp/." ) endif() set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) # add the binary tree to the search path for include files so that we will # find system.hh include_directories("${PROJECT_BINARY_DIR}") configure_file( ${PROJECT_SOURCE_DIR}/src/system.hh.in ${PROJECT_BINARY_DIR}/system.hh) if (CMAKE_CXX_COMPILER MATCHES "clang") set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem ") elseif(CMAKE_CXX_COMPILER MATCHES "g\\+\\+") set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem ") endif() add_subdirectory(src) add_subdirectory(doc) if (BUILD_EMACSLISP) add_subdirectory(lisp) endif() add_subdirectory(test) ######################################################################## # build a CPack driven installer package include (InstallRequiredSystemLibraries) set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.md") set (CPACK_PACKAGE_VERSION_MAJOR "${Ledger_VERSION_MAJOR}") set (CPACK_PACKAGE_VERSION_MINOR "${Ledger_VERSION_MINOR}") set (CPACK_PACKAGE_VERSION_PATCH "${Ledger_VERSION_PATCH}${Ledger_VERSION_PRERELEASE}") set (CPACK_GENERATOR "TBZ2") set (CPACK_SOURCE_GENERATOR "TBZ2") set (CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") set (CPACK_SOURCE_IGNORE_FILES "/.git*;/.dir-locals.el;~$;/doc/website/;/doc/wiki/;/lib/*.sh;/lib/Makefile;/tools/;${CPACK_SOURCE_IGNORE_FILES}") include (CPack) ### CMakeLists.txt ends here ledger-3.1.1+dfsg1/CONTRIBUTING.md000066400000000000000000000101651266023441000162100ustar00rootroot00000000000000Tips for contributors --------------------- * Please **make pull requests against `next`, not `master`**. Ledger follows a [git-flow] branching model, in which development happens on the `next` branch and is subsequently merged into `master` for releases. * If you're making **changes to `ledger-mode`, or other files for which the Travis build is not relevant**, please **add `[ci skip]` to the end of the commit message**. GLOSSARY ---- Developing the Ledger software uses a number different tools, not all of which will be familiar to all developers. **[Boost]**: a standard set of C++ libraries. Most Boost libraries consist of inline functions and templates in header files. **[Boost.Python]**: C++ library which enables seamless interoperability between C++ and the Python programming language. **[Cheetah]**: a Python templating engine, used by `./python/server.py`. **[CMake]**: A cross platform system for building from source code. It uses the `CMakeLists.txt` files. **[Doxygen]**: generates programming documentation from source code files. Primarily used on C++ sources, but works on all. Uses the `doc/Doxyfile.in` file. **[GCC]**: Gnu Compiler Collection, which includes the *gcc* compiler and *gcov* coverage/profiler tool. **[clang]**: C language family frontend for LLVM, which includes the *clang* compiler. **[GMP]**: Gnu Multiple Precision Arithmetic Library provides arbitrary precision math. **[MPFR]**: Gnu Multiple Precision Floating-point Library with correct rounding. **[Markdown]**: A typesetter format that produces *html* files from *.md* files. Note that GitHub automatically renders *.md* files. **[SHA1]**: a marginally secure cryptographic hash function, used only for signing the license file. **[Texinfo]**: Gnu documentation typesetter that produces *html* and *pdf* files from the `doc/\*.texi` files. **[Travis CI]**: a hosted continuous integration service that builds and runs tests each commit posted to GitHub. Each build creates a [log], updates a [small badge] at the top left of the main project's [README.md], and emails the author of the commit if any tests fail. **[utfcpp]**: a library for handling utf-8 in a variety of C++ versions. Orientation --- The source tree can be confusing to a new developer. Here is a selective orientation: **./acprep**: a custom thousand-line script to install dependencies, grab updates, and build. It also creates `\*.cmake`, `./CmakeFiles/` and other CMake temporary files. Use `./acprep --help` for more information. **./README.md**: user readme file in markdown format, also used as the project description on GitHub. **./contrib/**: contributed scripts of random quality and completion. They usually require editing to run. **./doc/**: documentation, licenses, and tools for generating documents such as the *pdf* manual. **./lib/**: a couple of libraries used in development. **./lisp/**: the [Emacs][] [ledger-mode] lisp code, under the [GPLv2] license. **./python/**: samples using the Python ledger module. **./src/**: the C++ header and source files in a flat directory. **./test/**: a testing harness with subdirectories full of tests **./tools/**: an accretion of tools, mostly small scripts, to aid development [Boost]: http://boost.org [Boost.Python]: http://www.boost.org/libs/python/ [GMP]: http://gmplib.org/ [MPFR]: http://www.mpfr.org/ [Cheetah]: http://www.cheetahtemplate.org [CMake]: http://www.cmake.org [Doxygen]: http://doxygen.org [Markdown]: https://daringfireball.net/projects/markdown/ [SHA1]: http://en.wikipedia.org/wiki/SHA-1 [Texinfo]: http://www.gnu.org/software/texinfo/ [Travis CI]: https://travis-ci.org [GCC]: http://gcc.gnu.org [utfcpp]: http://utfcpp.sourceforge.net [log]: https://travis-ci.org/ledger/ledger [small badge]: https://img.shields.io/travis/ledger/ledger/master.svg?&style=flat [git-flow]: http://nvie.com/posts/a-successful-git-branching-model/ [README.md]: https://github.com/ledger/ledger/blob/master/README.md [Emacs]: http://www.gnu.org/software/emacs/ [ledger-mode]: http://ledger-cli.org/3.0/doc/ledger-mode.html [GPLv2]: http://www.gnu.org/licenses/gpl-2.0.html [clang]: http://clang.llvm.org ledger-3.1.1+dfsg1/INSTALL.md000066400000000000000000000153321266023441000154100ustar00rootroot00000000000000# INSTALL To build this code after doing a Git clone, run: $ ./acprep update 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. Now install it: $ sudo make install ## 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 using: $ ./acprep dependencies The first order of business if acprep update doesn't work is to find out where things went wrong. So follow these steps to produce a bug report I can track down easily: $ ./acprep --debug update # shows what acprep was thinking $ $EDITOR CMakeCache.txt # shows what cmake was thinking 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` A: You didn't run `./acprep update`. ---------------------------------------------------------------------- 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): $ sudo apt-get install build-essential cmake texinfo python-dev \ zlib1g-dev libbz2-dev libgmp3-dev gettext libmpfr-dev \ libboost-date-time-dev libboost-filesystem-dev \ libboost-graph-dev libboost-iostreams-dev \ libboost-python-dev libboost-regex-dev libboost-test-dev \ doxygen libedit-dev libmpc-dev ---------------------------------------------------------------------- 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 until you see the exact compile error. Usually it's failing because your include directory is different from anything acprep is expecting to see. It could also be failing because your Boost libraries have a custom "suffix" on them. Let's say your Boost was installed in ~/boost, and every library has the suffix `-xgcc42`. This is what you would run: $ CPPFLAGS=-I$HOME/boost acprep --boost=xgcc42 update ---------------------------------------------------------------------- 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. 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. ---------------------------------------------------------------------- Q: I'm seeing a segfault deep inside the boost_regex code! A: Actually, the real segfault is in libstdc++'s facet code. It's being caused by using a debug Boost with a non-debug build of Ledger, or vice-versa. ---------------------------------------------------------------------- Q: Something else fails, or Ledger crashes on startup 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 ... runs till crash ... (gdb) bt Put that backtrace output, and the output from `ledger --version` in the bug report. ---------------------------------------------------------------------- Q: Whenever I try to use the Python support, I get a segfault A: Make sure that the boost_python library you linked against is using the exact same Python as the Ledger executable. In particular I see this bug on OS X systems where boost_python is linked against the default Python, while Ledger is linked against the version provided by MacPorts. Or vice versa. Solution: Use one or the other. If you prefer the system Python, run `port deactivate -f python26`, to get MacPorts' version out of the way. You'll then need to delete the Ledger binary and run `make` to relink it. ---------------------------------------------------------------------- Q: When I run `make check`, the Python unit tests always crash 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 Boos $ export BOOST_VERSION=1.57.0 $ cd /somewhere/you/want/to/build/boost $ wget -N http://iweb.dl.sourceforge.net/project/boost/boost/$BOOST_VERSION/boost_${BOOST_VERSION//./_}.tar.bz2 $ tar xvf boost_${BOOST_VERSION//./_}.tar.bz2 $ cd boost_${BOOST_VERSION//./_} $ ./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 $ export CMAKE_VERSION=3.1.0 $ cd /somewhere/you/want/to/build/cmake $ wget -N http://www.cmake.org/files/v${CMAKE_VERSION:0:3}/cmake-${CMAKE_VERSION}.tar.gz $ tar xvf cmake-${CMAKE_VERSION}.tar.gz $ cd cmake-${CMAKE_VERSION} $ ./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 PREFIX=/where/you/want/ledger/installed $SHELL $ ./acprep --prefix=$PREFIX --debug --python config $ ./acprep --prefix=$PREFIX --debug --python make $ ./acprep --prefix=$PREFIX --debug --python install ledger-3.1.1+dfsg1/LICENSE.md000066400000000000000000000027241266023441000153650ustar00rootroot00000000000000Copyright (c) 2003-2016, 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. ledger-3.1.1+dfsg1/README.md000066400000000000000000000207031266023441000152350ustar00rootroot00000000000000[![Build Status master](https://img.shields.io/travis/ledger/ledger/master.svg?label=master&style=flat)](https://travis-ci.org/ledger/ledger) [![Build Status next](https://img.shields.io/travis/ledger/ledger/next.svg?label=next&style=flat)](https://travis-ci.org/ledger/ledger) [![Status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/ledger/ledger/pulse/monthly) [![License](https://img.shields.io/badge/license-BSD-blue.svg?style=flat)](http://opensource.org/licenses/BSD-3-Clause) [![GitHub release](https://img.shields.io/github/release/ledger/ledger.svg?style=flat)](https://github.com/ledger/ledger/releases) # Ledger: Command-Line Accounting Ledger is a powerful, double-entry accounting system that is accessed from the UNIX command-line. This may put off some users, since there is no flashy UI, but for those who want unparalleled reporting access to their data there are few alternatives. Ledger uses text files for input. It reads the files and generates reports; there is no other database or stored state. To use Ledger, you create a file of your account names and transactions, run from the command line with some options to specify input and requested reports, and get output. The output is generally plain text, though you could generate a graph or html instead. Ledger is simple in concept, surprisingly rich in ability, and easy to use. ## For the Impatient 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/ledger/ledger.git $ cd ledger && ./acprep update # Update to the latest, configure, make Now try your first ledger command: $ ./ledger -f test/input/sample.dat reg For help on keeping your journal have a look at the [documentation] and the [wiki][] (Also see the “Resources” section at the end of this file). An emacs mode for ledger files can be found in the `lisp` directory and a vim plugin is located in the [ledger/vim-ledger repository]. ## To the Rest If you're reading this file, you have in your hands the Bleeding Edge. This may very well *not* be what you want, since it's not guaranteed to be in a functionally complete state. It's under active development, and may change in any way at any time. What you may prefer is the **CURRENT** stable release, or the **BETA** branch. Branch | Command -------|-------- **RELEASE** | `git checkout -b stable v3.1` | **CURRENT** | `git checkout -b master origin/master` | **BETA** | `git checkout -b 3.1.1 release/3.1.1` | **ALPHA** | `git checkout -b next origin/next` | There are also several topic branches which contain experimental features, though none of these are guaranteed to compile. Best to chat with me on [IRC] or via the [mailing list] before going too much further with those. ## Dependencies If you wish to proceed in this venture, you'll need a few dependencies. The easiest way to get them for your platform is to run this handy Python script: $ ./acprep dependencies If that doesn't completely work, here are the dependencies for building the current `master` branch: Dependency | Version (or greater) -----------|--------------------- [Boost] | 1.49 [GMP] | 4.2.2 [MPFR] | 2.4.0 [utfcpp] | 2.3.4 [gettext] | 0.17 _optional_ [libedit] | 20090111-3.0 _optional_ [Python] | 2.4 _optional_ [doxygen] | 1.5.7.1 _optional_, for `make docs` [graphviz] | 2.20.3 _optional_, for `make docs` [texinfo] | 4.13 _optional_, for `make docs` [lcov] | 1.6 _optional_, for `make report`, used with `/./acprep gcov` [sloccount] | 2.26 _optional_, for `make sloc` And for building the outdated `release/2.6.3` branch: Dependency | Version -----------|-------- [GMP] | 4.2.2 [pcre] | 7.7 [libofx] | 0.8.3 _optional_ [expat] | 2.0.1 _optional_ [libxml2] | 2.7.2 _optional_ ### Mac OS X You can use [Homebrew] or [MacPorts] to install Ledger easily on OS X. #### 1. Homebrew You can see the parameters you can pass while installing with brew by the command `brew options ledger`. To install ledger, simply type the following command: $ brew install ledger If everything worked well, you should have ledger working now. If you want to install this with python bindings, you can use the following command: $ brew install ledger --with-python If you to want to startup python, use the following command: $ ledger python #### 2. MacPorts If you build stuff using MacPorts on OS X, as I do, here is what you would run: $ sudo port install -f cmake python26 \ libiconv +universal zlib +universal gmp +universal \ mpfr +universal ncurses +universal ncursesw +universal \ gettext +universal libedit +universal boost-jam \ boost +st+python26+icu texlive doxygen graphviz \ texinfo lcov sloccount ### Ubuntu If you're going to build on Ubuntu, `sudo apt-get install ...` the following packages (current as of Ubuntu 14.04): $ sudo apt-get install build-essential cmake doxygen \ libboost-system-dev libboost-dev python-dev gettext git \ libboost-date-time-dev libboost-filesystem-dev \ libboost-iostreams-dev libboost-python-dev libboost-regex-dev \ libboost-test-dev libedit-dev libgmp3-dev libmpfr-dev texinfo Or, for Ubuntu 12.04: $ sudo apt-get install build-essential cmake zlib1g-dev libbz2-dev \ 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 libboost-iostreams-dev libboost-test-dev ### Debian Debian squeeze (6.0): the version of boost in squeeze is too old for ledger and unfortunately no backport is available at the moment. Debian 7 (wheezy), Debian 8 (jessie), Debian testing (stretch) and Debian unstable (sid) contain all components needed to build ledger. You can install all required build dependencies using the following command: $ sudo apt-get install build-essential cmake autopoint texinfo python-dev \ zlib1g-dev libbz2-dev libgmp3-dev gettext libmpfr-dev \ libboost-date-time-dev libboost-filesystem-dev \ libboost-graph-dev libboost-iostreams-dev \ libboost-python-dev libboost-regex-dev libboost-test-dev ## Building The next step is preparing your environment for building. While you can use `cmake .` and make, I've prepared a script that does a lot more of the footwork for you: $ ./acprep update # or, if you want to use the Boost libraries with suffix -mt, install in # $HOME/local and build with 2 processes in parallel $ ./acprep update --boost-suffix=-mt --prefix=$HOME/local -j2 Please read the contents of `CMakeFiles/CMakeOutput.log` and `CMakeFiles/CMakeError.log` if the configure step fails. Also, see the `help` subcommand to `acprep`, which explains some of its many options. It's pretty much the only command I run for configuring, building and testing Ledger. You can run `make check` to confirm the result, and `make install` to install. ## Resources Now that you're up and running, here are a few resources to keep in mind: - [Homepage] - [Documentation] - [IRC channel][IRC] - [Mailing List / Forum][mailing list] - [GitHub project page][github] - [Code analysis][openhub] 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][github], hack as much as you like, then send me a pull request via GitHub. [Homepage]: http://ledger-cli.org/ [documentation]: http://www.ledger-cli.org/docs.html [mailing list]: http://list.ledger-cli.org/ [wiki]: http://wiki.ledger-cli.org/ [IRC]: irc://irc.freenode.net/ledger [github]: http://github.com/ledger/ledger [ledger/vim-ledger repository]: https://github.com/ledger/vim-ledger [Homebrew]: http://brew.sh/ [MacPorts]: https://www.macports.org/ [Boost]: http://boost.org [GMP]: http://gmplib.org/ [MPFR]: http://www.mpfr.org/ [utfcpp]: http://utfcpp.sourceforge.net [gettext]: https://www.gnu.org/software/gettext/ [libedit]: http://thrysoee.dk/editline/ [Python]: http://python.org [doxygen]: http://www.doxygen.org/ [graphviz]: http://graphviz.org/ [texinfo]: http://www.gnu.org/software/texinfo/ [lcov]: http://ltp.sourceforge.net/coverage/lcov.php [sloccount]: http://www.dwheeler.com/sloccount/ [pcre]: http://www.pcre.org/ [libofx]: http://libofx.sourceforge.net [expat]: http://www.libexpat.org [libxml2]: http://xmlsoft.org [openhub]: https://www.openhub.net/p/ledger ledger-3.1.1+dfsg1/acprep000077500000000000000000001247651266023441000151730ustar00rootroot00000000000000#!/usr/bin/env python # acprep, version 3.1 # # This script simply sets up the compiler and linker flags for all the various # build permutations I use for testing and profiling. import inspect import logging import logging.handlers import optparse import os import re import shutil import string import sys import time import tempfile import datetime try: import hashlib except: import md5 from os.path import * from stat import * from subprocess import Popen, PIPE, call LEVELS = {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL} def which(program): def is_exe(fpath): return os.path.exists(fpath) and os.access(fpath, os.X_OK) def ext_candidates(fpath): yield fpath for ext in os.environ.get("PATHEXT", "").split(os.pathsep): yield fpath + ext fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) for candidate in ext_candidates(exe_file): if is_exe(candidate): return candidate return None class BoostInfo(object): def dependencies(self, system): if system == 'darwin-homebrew': return [ 'boost' ] if system == 'darwin-macports': return [ 'boost-jam', 'boost', '+python27+universal' ] if system == 'centos': return [ 'boost-devel' ] elif system == 'ubuntu-trusty': return [ 'libboost-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', 'libboost-iostreams-dev', 'libboost-python-dev', 'libboost-regex-dev', 'libboost-system-dev', 'libboost-test-dev' ] elif system == 'ubuntu-saucy' or system == 'ubuntu-precise': return [ 'autopoint', 'libboost-dev', 'libboost-test-dev', 'libboost-regex-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', 'libboost-iostreams-dev', 'libboost-python-dev' ] elif system == 'ubuntu-lucid': return [ 'bjam', 'autopoint', 'libboost-dev', 'libboost-regex-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', 'libboost-iostreams-dev', 'libboost-python-dev' ] class CommandLineApp(object): "Base class for building command line applications." force_exit = True # If true, always ends run() with sys.exit() log_handler = None boost_major = "1_52" def __init__(self): "Initialize CommandLineApp." # Create the logger self.log = logging.getLogger(os.path.basename(sys.argv[0])) ch = logging.StreamHandler() formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s") ch.setFormatter(formatter) self.log.addHandler(ch) self.log_handler = ch # Setup the options parser usage = 'usage: %prog [OPTIONS...] [ARGS...]' op = self.option_parser = optparse.OptionParser(usage = usage, conflict_handler = 'resolve') op.add_option('', '--debug', action='store_true', dest='debug', default=False, help='show debug messages and pass exceptions') op.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False, help='show informational messages') op.add_option('-q', '--quiet', action='store_true', dest='quiet', default=False, help='do not show log messages on console') op.add_option('', '--log', metavar='FILE', type='string', action='store', dest='logfile', default=False, help='append logging data to FILE') op.add_option('', '--loglevel', metavar='LEVEL', type='string', action='store', dest='loglevel', default=False, help='set log level: DEBUG, INFO, WARNING, ERROR, CRITICAL') self.options = op.get_default_values() def main(self, *args): """Main body of your application. This is the main portion of the app, and is run after all of the arguments are processed. Override this method to implment the primary processing section of your application.""" pass def handleInterrupt(self): """Called when the program is interrupted via Control-C or SIGINT. Returns exit code.""" self.log.error('Canceled by user.') return 1 def handleMainException(self): "Invoked when there is an error in the main() method." if not self.options.debug: self.log.exception('Caught exception') return 1 ## INTERNALS (Subclasses should not need to override these methods) def run(self): """Entry point. 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(values=self.options) if self.options.logfile: fh = logging.handlers.RotatingFileHandler(self.options.logfile, maxBytes = (1024 * 1024), backupCount = 5) formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s") fh.setFormatter(formatter) self.log.addHandler(fh) if self.options.quiet: self.log.removeHandler(self.log_handler) ch = logging.handlers.SysLogHandler() formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s") ch.setFormatter(formatter) self.log.addHandler(ch) self.log_handler = ch if self.options.loglevel: self.log.setLevel(LEVELS[self.options.loglevel]) elif self.options.debug: self.log.setLevel(logging.DEBUG) elif self.options.verbose: self.log.setLevel(logging.INFO) exit_code = 0 try: # We could just call main() and catch a TypeError, but that would # not let us differentiate between application errors and a case # where the user has not passed us enough arguments. So, we check # the argument count ourself. argspec = inspect.getargspec(self.main) expected_arg_count = len(argspec[0]) - 1 if len(main_args) >= expected_arg_count: exit_code = self.main(*main_args) else: self.log.debug('Incorrect argument count (expected %d, got %d)' % (expected_arg_count, len(main_args))) self.option_parser.print_help() exit_code = 1 except KeyboardInterrupt: exit_code = self.handleInterrupt() except SystemExit as msg: exit_code = msg.args[0] except Exception: exit_code = self.handleMainException() if self.options.debug: raise if self.force_exit: sys.exit(exit_code) return exit_code class PrepareBuild(CommandLineApp): ######################################################################### # Initialization routines # ######################################################################### def initialize(self): self.log.debug('Initializing all state variables') self.should_clean = False self.configured = False self.current_ver = None #self.current_flavor = 'default' self.current_flavor = 'debug' self.products_dir = None self.configure_args = [] self.CXXFLAGS = [] self.LDFLAGS = [] self.envvars = { 'CXX': 'g++', 'CXXFLAGS': '', 'LDFLAGS': '', } for varname in self.envvars.keys(): if varname in os.environ: self.envvars[varname] = os.environ[varname] if varname.endswith('FLAGS'): self.__dict__[varname] = str.split(os.environ[varname]) self.envvars[varname] = '' # If ~/Products/ or build/ exists, use them instead of the source tree # for building products = self.default_products_directory() if (exists(products) and isdir(products)) or \ (exists('build') and isdir('build')): self.options.build_dir = None def __init__(self): CommandLineApp.__init__(self) self.log.setLevel(logging.INFO) self.source_dir = os.getcwd() op = self.option_parser op.add_option('', '--help', action="callback", callback=self.option_help, help='Show this help text') op.add_option('-j', '--jobs', metavar='N', type='int', action='store', dest='jobs', default=1, help='Allow N make jobs at once') op.add_option('', '--boost', metavar='BOOST_ROOT', action="store", dest="boost_root", help='Set Boost library root (ex: "--boost=/usr/local")') op.add_option('', '--boost-suffix', metavar='BOOST_SUFFIX', action="store", dest="boost_suffix", help='Set Boost library suffix (ex: "--boost-suffix=-mt")') op.add_option('', '--boost-include', metavar='BOOST_INCLUDE', action="store", dest="boost_include", help='Set Boost include path (ex: "--boost-include=DIR")') op.add_option('', '--compiler', metavar='COMPILER', action="store", dest="compiler", help='Use the Clang C++ compiler') op.add_option('', '--cxx', metavar='COMPILER', 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') op.add_option('', '--doxygen', action='store_true', dest='enable_doxygen', default=False, 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') 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] args = args[1:] if args: cmd = args[0] if 'phase_' + cmd not in PrepareBuild.__dict__: self.log.error("Unknown build phase: " + cmd + "\n") sys.exit(1) else: args = args[1:] else: cmd = 'config' self.log.info('Invoking primary phase: ' + cmd) PrepareBuild.__dict__['phase_' + cmd](self, *args) ######################################################################### # General utility code # ######################################################################### def execute(self, *args): try: self.log.debug('Executing command: ' + ' '.join(args)) retcode = call(args, shell=False) if retcode < 0: self.log.error("Child was terminated by signal", -retcode) sys.exit(1) elif retcode != 0: self.log.error("Execution failed: " + ' '.join(args)) sys.exit(1) except OSError as e: self.log.error("Execution failed: " + e) sys.exit(1) def get_stdout(self, *args): try: self.log.debug('Executing command: ' + ' '.join(args)) proc = Popen(args, shell=False, stdout=PIPE) stdout = proc.stdout.read() retcode = proc.wait() if retcode < 0: self.log.error("Child was terminated by signal", -retcode) sys.exit(1) elif retcode != 0: self.log.error("Execution failed: " + ' '.join(args)) sys.exit(1) return stdout[:-1] except OSError as e: self.log.error("Execution failed:" + e) sys.exit(1) def isnewer(self, file1, file2): "Check if file1 is newer than file2." if not exists(file2): return True return os.stat(file1)[ST_MTIME] > os.stat(file2)[ST_MTIME] ######################################################################### # Determine information about the surroundings # ######################################################################### def prefix_directory(self): if self.options.prefix_dir: return self.options.prefix_dir else: return None def default_products_directory(self): return join(os.environ['HOME'], "Products") def products_directory(self): if not self.products_dir: products = self.default_products_directory() if not exists(products) or not isdir(products): products = join(self.source_dir, 'build') products = join(products, basename(self.source_dir)) self.products_dir = products return self.products_dir def build_directory(self): 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): self.log.info('Making directory: ' + dirname) os.makedirs(dirname) elif not isdir(dirname): self.log.error('Directory is not a directory: ' + dirname) sys.exit(1) return dirname def git_working_tree(self): return exists('.git') and isdir('.git') and not self.options.no_git def current_version(self): if not self.current_ver: major, minor, patch, date = None, None, None, None version_m4 = open('CMakeLists.txt', 'r') for line in version_m4.readlines(): match = re.match('^set\(Ledger_VERSION_MAJOR ([0-9]+)\)', line) if match: major = match.group(1) match = re.match('^set\(Ledger_VERSION_MINOR ([0-9]+)\)', line) if match: minor = match.group(1) match = re.match('^set\(Ledger_VERSION_PATCH ([0-9]+)\)', line) if match: patch = match.group(1) match = re.match('^set\(Ledger_VERSION_DATE ([0-9]+)\)', line) if match: date = match.group(1) break self.current_ver = "%s.%s.%s%s" % (major, minor, patch, "-%s" % date if date else "") version_m4.close() return self.current_ver def phase_products(self, *args): self.log.info('Executing phase: products') print(self.products_directory()) def phase_info(self, *args): self.log.info('Executing phase: info') environ, conf_args = self.configure_environment() self.log.info("Current version => " + self.current_version()) self.log.info("Current flavor => " + self.current_flavor) self.log.info("Source directory => " + self.source_dir) if self.prefix_directory(): self.log.info("Installation prefix => " + self.prefix_directory()) self.log.info("Products directory => " + self.products_directory()) self.log.info("Build directory => " + self.build_directory()) self.log.debug('CMake environment =>') keys = environ.keys() keys.sort() for key in keys: if key in ['PATH', 'CXX'] or key.endswith('FLAGS'): self.log.debug(' %s=%s' % (key, environ[key])) self.log.debug('CMake arguments =>') for arg in conf_args + list(args): self.log.debug(' %s' % arg) def phase_sloc(self, *args): self.log.info('Executing phase: sloc') self.execute('sloccount', 'src', 'python', 'lisp', 'test') ######################################################################### # Update local files with the latest information # ######################################################################### def phase_pull(self, *args): self.log.info('Executing phase: pull') if self.git_working_tree(): self.execute('git', 'pull') ######################################################################### # Automatic installation of build dependencies # ######################################################################### def phase_dependencies(self, *args): self.log.info('Executing phase: dependencies') self.log.info("Installing Ledger's build dependencies ...") system = self.get_stdout('uname', '-s') if system == 'Darwin': if exists('/opt/local/bin/port'): self.log.info('Looks like you are using MacPorts on OS X') packages = [ 'sudo', 'port', 'install', '-f', 'automake', 'autoconf', 'libtool', 'python27', '+universal', 'libiconv', '+universal', 'zlib', '+universal', 'gmp' ,'+universal', 'mpfr', '+universal', 'ncurses', '+universal', 'ncursesw', '+universal', 'gettext' ,'+universal', 'libedit' ,'+universal', 'texlive-xetex', 'doxygen', 'graphviz', 'texinfo', 'lcov', 'sloccount' ] + BoostInfo().dependencies('darwin-macports') self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) elif exists('/usr/local/bin/brew') or exists('/opt/local/bin/brew'): self.log.info('Looks like you are using Homebrew on OS X') packages = [ 'brew', 'install', 'cmake', 'ninja', 'mpfr', 'gmp', ] + BoostInfo().dependencies('darwin-homebrew') self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) elif exists('/sw/bin/fink'): self.log.info('Looks like you are using Fink on OS X') self.log.error("I don't know the package names for Fink yet!") sys.exit(1) elif system == 'Linux': if exists('/etc/issue'): issue = open('/etc/issue') if issue.readline().startswith('Ubuntu'): release = open('/etc/lsb-release') info = release.read() release.close() if re.search('trusty', info): self.log.info('Looks like you are using APT on Ubuntu Trusty') packages = [ 'sudo', 'apt-get', 'install', 'build-essential', 'doxygen', 'cmake', 'ninja-build', 'zlib1g-dev', 'libbz2-dev', 'python-dev', 'libgmp3-dev', 'libmpfr-dev', 'gettext', 'libedit-dev', 'texinfo', 'lcov', 'libutfcpp-dev', 'sloccount' ] + BoostInfo().dependencies('ubuntu-trusty') elif re.search('saucy', info): self.log.info('Looks like you are using APT on Ubuntu Saucy') packages = [ 'sudo', 'apt-get', 'install', 'build-essential', 'libtool', 'cmake', 'ninja-build', 'zlib1g-dev', 'libbz2-dev', 'python-dev', 'libgmp-dev', 'libmpfr-dev', 'gettext', 'libedit-dev', 'texinfo', 'lcov', 'sloccount' ] + BoostInfo().dependencies('ubuntu-saucy') elif re.search('precise', info): self.log.info('Looks like you are using APT on Ubuntu Precise') packages = [ 'sudo', 'apt-get', 'install', 'build-essential', 'libtool', 'cmake', 'zlib1g-dev', 'libbz2-dev', 'python-dev', 'libgmp-dev', 'libmpfr-dev', 'gettext', 'libedit-dev', 'texinfo', 'lcov', 'libutfcpp-dev', 'sloccount' ] + BoostInfo().dependencies('ubuntu-precise') else: self.log.info('I do not recognize your version of Ubuntu!') packages = None if packages: self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) if exists('/etc/redhat-release'): release = open('/etc/redhat-release').readline() if release.startswith('CentOS'): self.log.info('Looks like you are using YUM on CentOS') packages = [ 'sudo', 'yum', 'install', 'gcc', 'gcc-c++', 'compat-gcc-*', 'make', 'libtool', 'autoconf', 'automake', 'zlib-devel', 'bzip2-devel', 'python-devel', 'gmp-devel', 'gettext-devel', #'mpfr-devel' 'libedit-devel', #'texlive-full', #'doxygen', #'graphviz', 'texinfo', #'lcov', #'sloccount' ] self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) elif release.startswith('Fedora release 20'): self.log.info('Looks like you are using YUM on Fedora 20') packages = [ 'sudo', 'yum', 'install', 'boost-devel', 'bzip2-devel', 'cmake', 'doxygen', 'gcc', 'gcc-c++', 'gettext', 'gettext-devel', 'gmp-devel', 'lcov', 'libedit-devel', 'mpfr-devel', 'ninja-build', 'python-devel', 'sloccount', 'texinfo', 'zlib-devel' ] self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) elif system.startswith('CYGWIN'): self.log.info('Looks like you are using Cygwin') self.log.info('Please install the dependencies manually.') ######################################################################### # Determine the system's basic configuration # ######################################################################### def setup_for_johnw(self): self.configure_args.append('-DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON') if not self.options.compiler: self.configure_args.append('-DCMAKE_CXX_COMPILER:PATH=/usr/local/bin/clang++') if self.current_flavor == 'opt': 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') 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=/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): system = str(self.get_stdout('uname', '-s')) self.log.info('System type is => ' + system) if self.options.enable_doxygen: self.configure_args.append('-DUSE_DOXYGEN=1') if self.options.python: self.configure_args.append('-DUSE_PYTHON=1') if system.startswith('CYGWIN'): self.configure_args.append('-G') self.configure_args.append('Unix Makefiles') elif self.options.use_ninja: self.configure_args.append('-GNinja') if exists('/Users/johnw/Projects/ledger/plan/TODO'): self.setup_for_johnw() def setup_flavor(self): self.setup_for_system() if 'setup_flavor_' + self.current_flavor not in PrepareBuild.__dict__: self.log.error('Unknown build flavor "%s"' % self.current_flavor) sys.exit(1) self.log.info('Setting up build flavor => ' + self.current_flavor) PrepareBuild.__dict__['setup_flavor_' + self.current_flavor](self) def escape_string(self, data): return re.sub('(["\\\\])', '\\\\\\1', data) def finalize_config(self): self.setup_flavor() for var in ('CXXFLAGS', 'LDFLAGS'): value = self.__dict__[var] if value: first = not self.envvars[var] for member in value: #escaped = self.escape_string(member) #if member != escaped: # member = escaped if first: first = False else: self.envvars[var] += ' ' self.envvars[var] += member self.log.debug('Final value of %s: %s' % (var, self.envvars[var])) elif var in self.envvars: del self.envvars[var] ######################################################################### # Options that can modify any build flavor # ######################################################################### def option_local(self, option=None, opt_str=None, value=None, parser=None): self.log.debug('Saw option --local') self.options.build_dir = self.source_dir def option_help(self, option=None, opt_str=None, value=None, parser=None): self.phase_help() ######################################################################### # The various build flavors # ######################################################################### def setup_flavor_default(self): pass def setup_flavor_debug(self): self.configure_args.append('-DBUILD_DEBUG=1') def setup_flavor_opt(self): self.configure_args.append('-DNO_ASSERTS=1') def setup_flavor_gcov(self): # NO_ASSERTS is set so that branch coverage ignores the never-taken # else branch inside assert statements. self.configure_args.append('-DBUILD_DEBUG=1') self.configure_args.append('-DNO_ASSERTS=1') self.configure_args.append('-DCLANG_GCOV=1') self.CXXFLAGS.append('-fprofile-arcs') self.CXXFLAGS.append('-ftest-coverage') self.LDFLAGS.append('-fprofile-arcs') self.LDFLAGS.append('-ftest-coverage') if not self.options.compiler or self.options.compiler == "clang-3.1": self.LDFLAGS.append('-lgcov') def setup_flavor_gprof(self): self.configure_args.append('-DBUILD_DEBUG=1') self.CXXFLAGS.append('-pg') self.LDFLAGS.append('-pg') ######################################################################### # Configure build tree using CMake # ######################################################################### def configure_environment(self): self.finalize_config() environ = dict(os.environ) for key, value in self.envvars.items(): if value: environ[key] = value if self.build_directory() == self.source_dir: conf_args = ['cmake'] else: conf_args = ['cmake', self.source_dir] if not which('cmake'): self.log.error("Cannot find CMake, please check your PATH") sys.exit(1) for var in ('CXX', 'CXXFLAGS', 'LDFLAGS'): if self.envvars.get(var) and (var.endswith('FLAGS') or exists(self.envvars[var])): if var == 'CXX': conf_args.append('-DCMAKE_CXX_COMPILER=%s' % self.envvars[var]) elif var == 'CXXFLAGS': conf_args.append('-DCMAKE_CXX_FLAGS=%s' % self.envvars[var]) elif var == 'LDFLAGS': conf_args.append('-DCMAKE_EXE_LINKER_FLAGS=%s' % self.envvars[var]) if self.options.boost_root: conf_args.append('-DBOOST_ROOT=%s' % self.options.boost_root) conf_args.append('-DBoost_NO_SYSTEM_PATHS=TRUE') if self.options.boost_suffix: conf_args.append('-DBoost_COMPILER=%s' % self.options.boost_suffix) if self.options.boost_include: conf_args.append('-DBOOST_INCLUDEDIR=%s' % self.options.boost_include) if self.prefix_directory(): conf_args.append('-DCMAKE_INSTALL_PREFIX=%s' % self.prefix_directory()) return (environ, conf_args + self.configure_args) def phase_configure(self, *args): self.log.info('Executing phase: configure') self.configured = True environ, conf_args = self.configure_environment() for arg in args: if arg: conf_args.append(arg) build_dir = self.ensure(self.build_directory()) try: os.chdir(build_dir) 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) self.log.debug('configure env => ' + str(environ)) self.log.debug('configure args => ' + str(conf_args)) configure = Popen(conf_args, shell=False, env=environ) retcode = configure.wait() if retcode < 0: self.log.error("Child was terminated by signal", -retcode) sys.exit(1) elif retcode != 0: self.log.error("Execution failed: " + ' '.join(conf_args)) sys.exit(1) else: self.log.debug('configure does not need to be run') finally: os.chdir(self.source_dir) def phase_config(self, *args): self.log.info('Executing phase: config') self.phase_configure(*args) if self.should_clean: self.phase_clean() ######################################################################### # Builds products from the sources # ######################################################################### def phase_make(self, *args): self.log.info('Executing phase: make') config_args = [] make_args = [] for arg in args: 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) if self.options.verbose: make_args.append('-v' if self.options.use_ninja else 'VERBOSE=1') self.log.debug('Configure arguments => ' + str(config_args)) self.log.debug('Makefile arguments => ' + str(make_args)) if not self.configured: self.phase_config(*config_args) build_dir = self.ensure(self.build_directory()) try: self.log.debug('Changing directory to ' + build_dir) os.chdir(build_dir) 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: check') 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_make(*args) ######################################################################### # Build directory cleaning phases # ######################################################################### def phase_clean(self, *args): self.log.info('Executing phase: clean') self.phase_make('clean') def phase_gitclean(self, *args): self.log.info('Executing phase: gitclean') if self.git_working_tree(): self.execute('git', 'clean', '-dfx') ######################################################################### # Other build phases # ######################################################################### def configure_flavor(self, flavor, reset=True): self.initialize() # reset everything self.current_flavor = flavor 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()): self.log.info('=== Wiping build directory %s ===' % self.build_directory()) try: shutil.rmtree(self.build_directory()) except: self.execute('chmod', '-R', 'u+w', self.build_directory()) self.execute('rm', '-fr', self.build_directory()) def phase_rsync(self, *args): self.log.info('Executing phase: rsync') proof_dir = 'ledger-proof' if self.options.python: proof_dir += "-python" if self.options.compiler: proof_dir += "-" + basename(self.options.compiler) source_copy_dir = join(self.ensure(self.products_directory()), proof_dir) self.execute('rsync', '-a', '--delete', '--exclude=/dist/', '--exclude=.git/', '--exclude=b/', '--exclude=/lib/boost-release/', '--exclude=/archive/', '--exclude=/build/', '%s/' % self.source_dir, '%s/' % source_copy_dir) self.source_dir = source_copy_dir def phase_proof(self, *args): self.log.info('Executing phase: proof') self.log.info('=== Copying source tree ===') self.phase_rsync() self.phase_makeall(reset=True, *args) self.configure_flavor('opt', reset=False) self.log.info('=== Testing opt ===') # jww (2012-05-20): Can't use fullcheck yet #self.phase_make('fullcheck') self.phase_make('test') self.configure_flavor('gcov', reset=False) self.log.info('=== Testing gcov ===') #self.phase_make('check') self.phase_make('test') self.configure_flavor('default', reset=False) self.log.info('=== Testing default ===') #self.phase_make('fullcheck') self.phase_make('test') # jww (2012-05-20): docs are not working yet #self.phase_make('docs') self.configure_flavor('debug', reset=False) self.log.info('=== Testing debug ===') #self.phase_make('fullcheck') self.phase_make('test') def phase_makeall(self, reset=False, *args): self.log.info('Executing phase: makeall') self.configure_flavor('opt', reset) self.log.info('=== Building opt ===') self.phase_make(*args) self.configure_flavor('gcov', reset) self.log.info('=== Building gcov ===') self.phase_make(*args) self.configure_flavor('default', reset) self.log.info('=== Building default ===') self.phase_make(*args) self.configure_flavor('debug', reset) self.log.info('=== Building debug ===') self.phase_make(*args) ######################################################################### # Help # ######################################################################### def phase_help(self, *args): self.option_parser.print_help() print(""" Of the optional ARGS, the first is an optional build FLAVOR, with the default being 'debug': default Regular autoconf settings debug Debugging and --verify support (default) opt Full optimizations gcov Coverage analysis gprof Code profiling (for OS X, just use: 'shark -i ledger ...') Next is the optional build PHASE, with 'config' being the default: clean Runs 'make clean' in the build directory config Configure the environment for building dependencies Automatically install all necessary build dependencies gitclean Runs 'git clean -dfx', which *really* cleans things help Displays this help text info Show information about the build environment make Do a make in the build directory proof Proves Ledger by building and testing every flavor pull Pulls the latest, and updates local config if need be update Does it all, updates your environment and re-make's There are many other build phases, though most are not of interest to the typical user: configure Runs just cmake do_all Runs makeall followed by proof gettext Initialize gettext support makeall Build every flavor there is products Report the products directory path rsync Rsync a copy of the source tree into Products sloc Report total Source Lines Of Code version Output current HEAD version to version.m4 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 --python ./acprep opt make ./acprep make doc -- -DBUILD_WEB_DOCS=1""") sys.exit(0) PrepareBuild().run() ledger-3.1.1+dfsg1/cmake/000077500000000000000000000000001266023441000150345ustar00rootroot00000000000000ledger-3.1.1+dfsg1/cmake/FindUtfcpp.cmake000066400000000000000000000013431266023441000201010ustar00rootroot00000000000000# - Try to find utfcpp # Once done, this will define # # UTFCPP_FOUND - system has utfcpp's utf8.h # UTFCPP_PATH - the utfcpp include directories include(CheckCXXSourceCompiles) set(UTFCPP_FOUND FALSE) find_path(UTFCPP_INCLUDE_DIR NAMES utf8.h HINTS "${UTFCPP_PATH}" "${PROJECT_SOURCE_DIR}/lib/utfcpp/v2_0/source" ) if (UTFCPP_INCLUDE_DIR) set(CMAKE_REQUIRED_INCLUDES "${UTFCPP_INCLUDE_DIR}") set(UTFCPP_FOUND TRUE) endif() check_cxx_source_compiles(" #include #include \"utf8.h\" int main(int argc, char** argv) { std::string input = std::string(\"utfcpp\"); const char * p = input.c_str(); std::size_t len = input.length(); utf8::is_valid(p, p + len); }" HAVE_WORKING_UTFCPP) ledger-3.1.1+dfsg1/contrib/000077500000000000000000000000001266023441000154145ustar00rootroot00000000000000ledger-3.1.1+dfsg1/contrib/Makefile000066400000000000000000000001721266023441000170540ustar00rootroot00000000000000all: ParseCcStmt.exe ParseCcStmt.exe: ParseCcStmt.cs CSVReader.cs gmcs -out:ParseCcStmt.exe ParseCcStmt.cs CSVReader.cs ledger-3.1.1+dfsg1/contrib/ParseCcStmt.cs000066400000000000000000000130061266023441000201330ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using CSVReader; /** * @file ParseCcStmt.cs * * @brief Provides a .NET way to turn a CSV report into Ledger transactions. * * I use this code for converting the statements from my own credit card * issuer. I realize it's strange for this to be in C#, but I wrote it * during a phase of C# contracting. The code is solid enough now -- * and the Mono project is portable enough -- that I haven't seen the * need to rewrite it into another language like Python. */ namespace JohnWiegley { public class Posting { public DateTime Date; public DateTime PostedDate; public string Code; public string Payee; public Decimal Amount; } public interface IStatementConverter { List ConvertRecords(Stream s); } public class ConvertGoldMasterCardStatement : IStatementConverter { public List ConvertRecords(Stream s) { List posts = new List(); using (CSVReader.CSVReader csv = new CSVReader.CSVReader(s)) { string[] fields; while ((fields = csv.GetCSVLine()) != null) { if (fields[0] == "POSTING DATE") continue; Posting post = new Posting(); post.Date = DateTime.ParseEpost(fields[0], "mm/dd/yy", null); post.PostedDate = DateTime.ParseEpost(fields[1], "mm/dd/yy", null); post.Payee = fields[2].Trim(); post.Code = fields[3].Trim(); post.Amount = Convert.ToDecimal(fields[4].Trim()); if (post.Code.Length == 0) post.Code = null; posts.Add(post); } } return posts; } } public class ConvertMastercardStatement : IStatementConverter { public List ConvertRecords(Stream s) { List posts = new List(); using (CSVReader.CSVReader csv = new CSVReader.CSVReader(s)) { string[] fields; while ((fields = csv.GetCSVLine()) != null) { Posting post = new Posting(); post.Date = DateTime.ParseEpost(fields[0], "m/dd/yyyy", null); post.Payee = fields[2].Trim(); post.Code = fields[3].Trim(); post.Amount = - Convert.ToDecimal(fields[4].Trim()); if (post.Code.Length == 0) post.Code = null; posts.Add(post); } } return posts; } } public class PrintPostings { public string DefaultAccount(Posting post) { if (Regex.IsMatch(post.Payee, "IGA")) return "Expenses:Food"; return "Expenses:Food"; } public void Print(string AccountName, string PayAccountName, List posts) { foreach (Posting post in posts) { if (post.Amount < 0) { Console.WriteLine("{0} * {1}{2}", post.Date.ToString("yyyy/mm/dd"), post.Code != null ? "(" + post.Code + ") " : "", post.Payee); Console.WriteLine(" {0,-36}{1,12}", AccountName, "$" + (- post.Amount).ToString()); Console.WriteLine(" {0}", PayAccountName); } else { Console.WriteLine("{0} {1}{2}", post.Date.ToString("yyyy/mm/dd"), post.Code != null ? "(" + post.Code + ") " : "", post.Payee); Console.WriteLine(" {0,-36}{1,12}", DefaultAccount(post), "$" + post.Amount.ToString()); Console.WriteLine(" * {0}", AccountName); } Console.WriteLine(); } } } public class ParseCcStmt { public static int Main(string[] args) { StreamReader reader = new StreamReader(args[0]); string firstLine = reader.ReadLine(); string CardAccount = args[1]; string BankAccount = args[2]; IStatementConverter converter; if (firstLine.StartsWith("POSTING DATE")) { converter = new ConvertGoldMasterCardStatement(); } else { converter = new ConvertMastercardStatement(); } reader = new StreamReader(args[0]); List posts = converter.ConvertRecords(reader.BaseStream); PrintPostings printer = new PrintPostings(); printer.Print(CardAccount, BankAccount, posts); return 0; } } } ledger-3.1.1+dfsg1/contrib/README000066400000000000000000000002061266023441000162720ustar00rootroot00000000000000This scripts are provided just to give some ideas. They probably need to be modified to better suit your environment. Beware! John ledger-3.1.1+dfsg1/contrib/bal000077500000000000000000000005471266023441000161060ustar00rootroot00000000000000#!/bin/sh switch="-c" limit="-t (/Liabilities/?a<0:Ua>100)&a" if [ "$1" = "-C" -o "$1" = "-U" ]; then switch="$1" shift elif [ "$1" = "-b" -o "$1" = "-e" -o "$1" = "-p" ]; then switch="$1 $2" shift 2 fi accts="$@" if [ -z "$accts" ]; then accts="-Equity -Income -Expenses" else limit="" fi ledger -VQ $switch $limit -s -S "-UT" balance $accts ledger-3.1.1+dfsg1/contrib/bal-huquq000077500000000000000000000006051266023441000172420ustar00rootroot00000000000000#!/bin/sh switch="-c" limit="-t (/Liabilities/?(/Huquq/?a/P{2.22AU}<={-1.0}:a<0):Ua>100)&a" if [ "$1" = "-C" -o "$1" = "-U" ]; then switch="$1" shift elif [ "$1" = "-b" -o "$1" = "-e" -o "$1" = "-p" ]; then switch="$1 $2" shift 2 fi accts="$@" if [ -z "$accts" ]; then accts="-Equity -Income -Expenses" else limit="" fi ledger -VQ $switch $limit -s -S "-UT" balance $accts ledger-3.1.1+dfsg1/contrib/compilation-ledger.el000066400000000000000000000063711266023441000215230ustar00rootroot00000000000000;;; compilation-ledger.el --- error regexps for ledger ;; Copyright 2009, 2010, 2011 Kevin Ryde ;; Author: Kevin Ryde ;; Version: 1 ;; Keywords: processes ;; URL: http://user42.tuxfamily.org/compilation-ledger/index.html ;; EmacsWiki: CompilationMode ;; compilation-ledger.el 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, or (at your ;; option) any later version. ;; ;; compilation-ledger.el 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 can get a copy of the GNU General Public License online at ;; . ;;; Commentary: ;; This spot of code adds a `compilation-error-regexp-alist' pattern to ;; recognise error messages from the "ledger" program, ;; ;; http://newartisans.com/software/ledger.html ;; ;; such as ;; ;; Error: "foo.ledger", line 1656: Invalid date string: foo ;; ;; This is only for running ledger from M-x compile. ledger.el shows ;; reports with its own `ledger-report-mode' which has more features and ;; isn't based on `compilation-mode'. ;;; Install: ;; Put compilation-ledger.el in one of your `load-path' directories, ;; and in your .emacs add ;; ;; (eval-after-load "compile" '(require 'compilation-ledger)) ;; ;; There's an autoload cookie below for this, if you know how to use ;; `update-file-autoloads' and friends. ;;; Emacsen: ;; Designed for Emacs 20 up, works in XEmacs 21 too. ;;; History: ;; Version 1 - the first version ;;; Code: ;;;###autoload (eval-after-load "compile" '(require 'compilation-ledger)) (require 'compile) (let ((symbol 'compilation-ledger) (pattern '("^Error: \"\\([^\"\n]+?\\)\", line \\([0-9]+\\):" 1 2))) (cond ((eval-when-compile (boundp 'compilation-error-regexp-systems-list)) ;; xemacs21 (add-to-list 'compilation-error-regexp-alist-alist (list symbol pattern)) (compilation-build-compilation-error-regexp-alist)) ((eval-when-compile (boundp 'compilation-error-regexp-alist-alist)) ;; emacs22 up (add-to-list 'compilation-error-regexp-alist symbol) (add-to-list 'compilation-error-regexp-alist-alist (cons symbol pattern))) (t ;; emacs21 (add-to-list 'compilation-error-regexp-alist pattern)))) (defun compilation-ledger-unload-function () "Remove compilation-ledger regexps on `unload-feature'." (setq compilation-error-regexp-alist (remove 'compilation-ledger compilation-error-regexp-alist)) (setq compilation-error-regexp-alist-alist (remove (assq 'compilation-ledger compilation-error-regexp-alist-alist) compilation-error-regexp-alist-alist)) (when (eval-when-compile (fboundp 'compilation-build-compilation-error-regexp-alist)) (compilation-build-compilation-error-regexp-alist)) nil) ;; and normal unload-feature actions ;; LocalWords: http newartisans html el (provide 'compilation-ledger) ;;; compilation-ledger.el ends here ledger-3.1.1+dfsg1/contrib/entry000077500000000000000000000004331266023441000165030ustar00rootroot00000000000000#!/bin/sh if [ -z "$LEDGER" -o ! -r "$LEDGER" ]; then echo Please set your LEDGER environment variable. fi line=`wc -l $LEDGER | awk '{print $1}'` if ledger xact "$@" > /tmp/xact; then cat /tmp/xact >> $LEDGER else echo "$@" >> $LEDGER fi rm /tmp/xact vi +$line $LEDGER ledger-3.1.1+dfsg1/contrib/getquote-uk.py000077500000000000000000000007601266023441000202460ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib, string, sys def download(sym): url = "http://uk.old.finance.yahoo.com/d/quotes.csv?s=" url += sym + "&f=sl1d1t1c1ohgv&e=.csv" f = urllib.urlopen(url, proxies={}) info = f.read() f.close() fields = string.split(info, ',') result = float(fields[1])/100 return result sym = sys.argv[1] sym = sym.replace('_', '.') if sym == '£': print '£1.00' else: try: print "£" +str(download(sym)) except: pass ledger-3.1.1+dfsg1/contrib/getquote.pl000077500000000000000000000006401266023441000176110ustar00rootroot00000000000000#!/usr/bin/env perl $timeout = 60; use Finance::Quote; use POSIX qw(strftime localtime time); $q = Finance::Quote->new; $q->timeout($timeout); $q->require_labels(qw/price/); %quotes = $q->fetch("nasdaq", $ARGV[0]); if ($quotes{$ARGV[0], "price"}) { print strftime('%Y/%m/%d %H:%M:%S', localtime(time())); print " ", $ARGV[0], " "; print "\$", $quotes{$ARGV[0], "price"}, "\n"; } else { exit 1; } ledger-3.1.1+dfsg1/contrib/iso4127-commodities/000077500000000000000000000000001266023441000210365ustar00rootroot00000000000000ledger-3.1.1+dfsg1/contrib/iso4127-commodities/iso4217ledger.sh000077500000000000000000000037721266023441000237010ustar00rootroot00000000000000#!/bin/sh # iso4217ledger.sh - Convert ISO 4217 currencies to ledger commodities # # This script will download the latest XML for ISO 4217 Table A.1 # and print the contained currency & funds code list as ledger # commodity definitions to stdout. # Copyright (c) 2014 Alexis Hildebrandt # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. xml_url="http://www.currency-iso.org/dam/downloads/lists/list_one.xml" xsl_file="$(dirname $0)/iso4217ledger.xsl" xsltproc="$(which xsltproc)" if [ ! -f "$xsltproc" -o ! -x "$xsltproc" ]; then echo "Can't find xsltproc" exit 1 fi download_command="$(which curl)" if [ -f "$download_command" \ -a -x "$download_command" ]; then download_options="--silent" else download_command="$(which wget)" if [ -n "$download_command" \ -a -f "$download_command" \ -a -x "$download_command" ]; then download_options="--quiet --output-document -" else echo "Can't find curl or wget." exit 1 fi fi $download_command $download_options "$xml_url" | $xsltproc "$xsl_file" - ledger-3.1.1+dfsg1/contrib/iso4127-commodities/iso4217ledger.xsl000066400000000000000000000077741266023441000241000ustar00rootroot00000000000000 , ; Ledger commodity declarations ; Generated from ISO 4217 Table A.1 XML ( ) using iso4217ledger.xsl ¤ commodity note - ( ) format 0000 nomarket 0 ledger-3.1.1+dfsg1/contrib/ledger-completion.bash000066400000000000000000000105161266023441000216670ustar00rootroot00000000000000### Assumption # # bash-completion package is installed and enabled # ### Just want to try it? # # $ source ledger-completion.bash # ### How to install? # #### For local user # # $ cat <>~/.bash_completion # . ~/.bash_completion.d/ledger # EOF # # $ cp ledger-completion.bash ~/.bash_completion.d/ledger # #### For all users # # $ sudo cp ledger-completion.bash /etc/bash_completion.d/ledger # _ledger() { local cur prev command options COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" # COMMANDS # # Commands are found in source code: # report.cc::lookup "case symbol_t::COMMAND" # report.cc::lookupcase "case symbol_t::PRECOMMAND" : these are debug commands and they have been filtered out here # commands="accounts balance budget cleared commodities convert csv draft echo emacs entry equity lisp org payees pricemap prices pricesdb print register reload select source stats tags xact xml" # OPTIONS # # Options are found in source code: # global.cc::lookup_option # report.cc::lookup_option # session.cc::lookup_option # options="--abbrev-len= --account-width= --account= --actual --actual-dates --add-budget --amount-data --amount-width= --amount= --anon --ansi --args-only --auto-match --aux-date --average --balance-format= --base --basis --begin= --bold-if= --budget --budget-format= --by-payee --cache= --change --check-payees --cleared --cleared-format= --collapse --collapse-if-zero --color --columns= --cost --count --csv-format= --current --daily --date-format= --date-width= --date= --datetime-format= --day-break --days-of-week --dc --debug= --decimal-comma --depth= --detail --deviation --display-amount= --display-total= --display= --dow --download --effective --empty --end= --equity --exact --exchange= --explicit --file= --first= --flat --force-color --force-pager --forecast-while= --forecast-years= --forecast= --format= --full-help --gain --generated --group-by= --group-title-format= --head= --help --help-calc --help-comm --help-disp --historical --immediate --init-file= --inject= --input-date-format= --invert --last= --leeway= --limit= --lot-dates --lot-notes --lot-prices --lot-tags --lots --lots-actual --market --master-account= --meta-width= --meta= --monthly --no-aliases --no-color --no-pager --no-rounding --no-titles --no-total --now= --only= --options --output= --pager= --payee-width= --payee= --pedantic --pending --percent --period-sort= --period= --permissive --pivot= --plot-amount-format= --plot-total-format= --prepend-format= --prepend-width= --price --price-db= --price-exp= --pricedb-format= --prices-format= --primary-date --quantity --quarterly --raw --real --recursive-aliases --register-format= --related --related-all --revalued --revalued-only --revalued-total= --rich-data --script= --seed= --sort-all= --sort-xacts= --sort= --start-of-week= --strict --subtotal --tail= --time-colon --time-report --total-data --total-width= --total= --trace= --truncate= --unbudgeted --uncleared --unrealized --unrealized-gains= --unrealized-losses= --unround --value --value-expr= --values --verbose --verify --verify-memory --version --weekly --wide --yearly" # Bash FAQ E13 http://tiswww.case.edu/php/chet/bash/FAQ # COMP_WORDBREAKS=${COMP_WORDBREAKS//:} # ACCOUNTS # # Accounts are generated with bash command: # $ ledger accounts>/tmp/accounts; for i in {1..5}; do cut -d : -f $i- /tmp/accounts;cut -d : -f -$i /tmp/accounts; done|sort -u|xargs # # Warning: this is working badly if there are spaces in account names # accounts="Assets Liabilities Equity Revenue Expenses" case $prev in --@(cache|file|init-file|output|pager|price-db|script)) _filedir return 0 ;; @(balance|equity|print|register)) COMPREPLY=( $(compgen -W "${accounts}" -- ${cur}) ) return 0 ;; esac if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${options}" -- ${cur}) ) # elif [[ ${cur} == [A-Z]* ]] ; then # COMPREPLY=( $(compgen -W "${accounts}" -- ${cur}) ) else COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) ) fi return 0 } complete -F _ledger ledger # Local variables: # mode: shell-script # sh-basic-offset: 4 # sh-indent-comment: t # indent-tabs-mode: nil # End: # ex: ts=4 sw=4 et filetype=sh ledger-3.1.1+dfsg1/contrib/ledger-du000077500000000000000000000016661266023441000172230ustar00rootroot00000000000000#!/usr/bin/env python import string import sys import os import time from stat import * from os.path import * def report_file(path): dir_elems = string.split(dirname(path), os.sep) if dir_elems[0] == "." or dir_elems[0] == "": dir_elems = dir_elems[1 :] account = string.join(dir_elems, ":") info = os.stat(path) print time.strftime("%Y/%m/%d", time.localtime(info[ST_MTIME])), print basename(path) print " ", account, " ", info[ST_SIZE], "b" print " Equity:Files" print def find_files(path): xacts = os.listdir(path) for xact in xacts: xact = join(path, xact) if not islink(xact): if isdir(xact) and xact != "/proc": find_files(xact) else: report_file(xact) args = sys.argv[1:] if len(args): paths = args else: paths = ["."] print """ C 1.00 Kb = 1024 b C 1.00 Mb = 1024 Kb C 1.00 Gb = 1024 Mb C 1.00 Tb = 1024 Gb """ for path in paths: find_files(path) ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/000077500000000000000000000000001266023441000223075ustar00rootroot00000000000000ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/GPLv3000066400000000000000000001045131266023441000231310ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. 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. Copyright (C) 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 . 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: Copyright (C) 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 . 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 . ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/LICENSE000066400000000000000000000012771266023441000233230ustar00rootroot00000000000000Contents 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. ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/README000066400000000000000000000054241266023441000231740ustar00rootroot00000000000000README 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/ ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/bank-reconcilation.plx000077500000000000000000000171331266023441000266060ustar00rootroot00000000000000#!/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] <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: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx�����0000775�0000000�0000000�00000017547�12660234410�0032722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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) = ('-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'accounts'); 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: ���������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/csv2ods.py��������������������������������������0000775�0000000�0000000�00000023703�12660234410�0024254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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() �������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/demo.sh�����������������������������������������0000775�0000000�0000000�00000001744�12660234410�0023600�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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" ����������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/fund-report.plx���������������������������������0000775�0000000�0000000�00000017706�12660234410�0025317�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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: ����������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/general-ledger-report.plx�����������������������0000775�0000000�0000000�00000022127�12660234410�0027231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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, $beginDate" if $formattedBeginDate->parse($beginDate); $formattedBeginDate = $formattedBeginDate->printf("%Y/%m/%d"); my(@chartOfAccountsOpts) = ('-b', $beginDate, '-e', $endDate, @otherLedgerOpts, 'accounts'); 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: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/ooolib2/����������������������������������������0000775�0000000�0000000�00000000000�12660234410�0023654�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/ooolib2/__init__.py�����������������������������0000664�0000000�0000000�00000234546�12660234410�0026003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"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 ����������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/readcsv.py��������������������������������������0000775�0000000�0000000�00000002157�12660234410�0024320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/summary-reports.plx�����������������������������0000775�0000000�0000000�00000047374�12660234410�0026247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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/Fraud Receivable' => {args => [ '-e', $endDate, 'bal', '/^Accrued:(Loans|Fraud) 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/Fraud 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/Fraud 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}) > $TWO_CENTS); 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}) > $TWO_CENTS); 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 COMPLIANCE' => { args => [ '/^Income.*(Enforce|Compliance)/' ]}, '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 COMPLIANCE', '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}) > $TWO_CENTS); 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 COMPLIANCE' => { regex => '^Expenses.*(Enforce|Compliance)' }, '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 COMPLIANCE', '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) > $TWO_CENTS); 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: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0023451�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Financial/��������������������������������0000775�0000000�0000000�00000000000�12660234410�0025335�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Financial/BankStuff/����������������������0000775�0000000�0000000�00000000000�12660234410�0027220�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Financial/BankStuff/bank-statement.pdf����0000664�0000000�0000000�00000006271�12660234410�0032636�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������%PDF-1.4 %쏢 5 0 obj <</Length 6 0 R/Filter /FlateDecode>> stream xN0EY J, #1- j({N*ذ@eپ3�#0u]¦ehn؁a+$�m//F X!B yF�Ӣ+:_oL*.?_@±hB_͙ۜLpDG`*fggKf,Ii58@DԘBI1R<ī Bu-TK fN0LMȔ"jP(=gGJ6U*zVh_&Cʁ5i4=k^Η:)ʽ?#mM4(O4{e۲8j,P@͕@|ξendstream endobj 6 0 obj 341 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 14 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 /Encoding 13 0 R/Subtype/Type1>> endobj 13 0 obj <</Type/Encoding/Differences[ 45/minus]>> endobj 14 0 obj <</Type/Metadata /Subtype/XML/Length 1404>>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='67bdd46a-6c22-11ed-0000-635a4c31e8cd' xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 8.71'/> <rdf:Description rdf:about='67bdd46a-6c22-11ed-0000-635a4c31e8cd' xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2012-11-21T13:04:13-05:00</xmp:ModifyDate> <xmp:CreateDate>2012-11-21T13:04:13-05:00</xmp:CreateDate> <xmp:CreatorTool>a2ps version 4.14</xmp:CreatorTool></rdf:Description> <rdf:Description rdf:about='67bdd46a-6c22-11ed-0000-635a4c31e8cd' xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='67bdd46a-6c22-11ed-0000-635a4c31e8cd'/> <rdf:Description rdf:about='67bdd46a-6c22-11ed-0000-635a4c31e8cd' xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>bank-statement.txt</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:20121121130413-05'00') /ModDate(D:20121121130413-05'00') /Title(bank-statement.txt) /Author(Bradley M. Kuhn) /Creator(a2ps version 4.14)>>endobj xref 0 15 0000000000 65535 f 0000000664 00000 n 0000002601 00000 n 0000000605 00000 n 0000000445 00000 n 0000000015 00000 n 0000000426 00000 n 0000000729 00000 n 0000000914 00000 n 0000000850 00000 n 0000000976 00000 n 0000000770 00000 n 0000000800 00000 n 0000001062 00000 n 0000001120 00000 n trailer << /Size 15 /Root 1 0 R /Info 2 0 R /ID [<3CA79A8F2992E2C87D9DA71C96908F9F><3CA79A8F2992E2C87D9DA71C96908F9F>] >> startxref 2804 %%EOF ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Financial/Invoices/�����������������������0000775�0000000�0000000�00000000000�12660234410�0027114�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.pdf����0000664�0000000�0000000�00000033102�12660234410�0032014�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������%PDF-1.4 %쏢 5 0 obj <</Length 6 0 R/Filter /FlateDecode>> stream xm}Id;o"U^w3`W 5H(~=zc<?ϟ׿ WyRׇןig:5WyePu ~wO`{bJuWjO[]z%LFm&.$Z/(z*]lc *J缟hyD4ׂ^%0>Ҥe qX:(}?Qe<{vDo:F8ot+DH4 F٪M#꧷FrMctyF#f3LjEzRǜ>FCМ߀2Ad<Ԇ0yWZģy& $wEA!/7TDyJX*~{U5Fs@8*}w4 ƇH Y  (,P:<;F-(g-3g5$da30B5D[Mq:2ZعH(f^ o NI hwkYli3zgnIkq.'A X4"2ݙ)ӝ0 Z`ZR[TvğTT̿6u!C$D5v 3J;j0FF4*Rj*4_遑E 2x¦r?tGgp{<DKZ P уMYq 27J,-+7ZF<A ^F ) çgi]jvD%jӄ/(P[hט/t,ЌX;ĉ(0f6ZKǛ(v0RD/TWVkъ7T4)浕D<Ssţ5a9f`&i"S Xz *(P'GRdn^$%Xv`DG "чŢ?ZX#M:h}BuLh`%Sq~'cv<ǖr\ UoUgfօ^RF Ɯ()͑0=:fiLǐO ^P:FϵAg)Ae?58�q^ӵ"(HxIrGkC7Yl $FaTQ]J0 5(oѪƐEl/R-@V4PzM|1tA #SH,wv,<6_(;=MeZQ-<a!RPomZN+,'0&I Q%ǂmY X$L\í۠a܋bFkS@M&&* "- ',Hu /- o>(P4+h{%hC3<a)?? C&lSZ B%%̃ H\q(CM&إznS&KG9iKbSRA�K:{[,XnTQhqóyDlc~MVHOV#Kt#:2NJb@ƚ p ĀpV5uClL<kkXA; *6y8Lu!E^d'/RO [iZA ,K4ZcĝYj3Ӯ߿!;DUfXcusLD`b\w<l P˖�7;5Zp%5 CazY$IRe Kۉ|mϔ8@fbq$&G6XZxX%UnSJX4i=.̑ 2.|o yO~‚uda(4pnP@ESnTMyQS#BrA&V`8XiEmaT/8EXϾ:&Slu2ںҊǘ]*<}rJxV6Xl:)zWA /D I0 iXQ'X+ {,|1]oxFs>^"H:H`{a+FRGv]g=Qiap^t'ftk<ͩSEcHH`(ք"H7*D˒ijWHy 5h b1JY f2a7US`=S ^ @&qaHz-*EOUzBB%L+&ikJ`ȿiV35W?֓_@%.ئE‹ vā'ɑNU9up2)];#D,QۡU:6CK7\:4e{pf⋫+Y}!b6YI4ޞj.7GUGJ4h0Ɔ{18ϵأ.HTI^  &EM#W] 2{3.^k>jcuѿ7v6ʴ_l$q"r҇MA数Q,Zb<q'8IG ov'm1wT¡( ."6Ą o޴ UNLw![P?# Ye aR{Me҈#LVhskP%ػ0-5>[QW,#n?ȕMw:q:%X㴄@MkMk$5[ ّ#yBw3L劬 - .|Ѹƈ#bO ]3zĢd֓/G,2F-ŕ ~xO@!?jy,f;fqGWqӯmAYY Lwj!aH3a~yDPY@l2!jc%„QeHiz5Pdy`O_+^=$*Œ9ij 8^Mՙԁ`m`,^ īMWīyϸM/ǫWJ>ǫ�S@Ytī{?Wx]s WY*Ɣ,>+^=X])^ J`W!Iyxuvū{+^ X@Hi*|ūf@aWc~KohzhxUx$\*rJj@:BսgG hOQjopxO;x4Yъ]A73ssh~ sv9 D7㈃ͳ9h9BȉBm4$;s$X2Sz9̊(σ0AuP`^Qj_ݦCMOa+?'Bk d@uBBQSyh kWZ+w`a+ `#102/cmZ;6-$N:HM9j[&:iZ4XG a@nNƃkCʀAa߆j&xK>!xY7xqM9i>g.t�ђo`ܞreܘN!wvjl㰖MD4Kz0*SpM:h]:BHBqt~/FPfr%αtn/wPÑ!S57wCg4o 3s,߄h mSVң\~1hu%Dڛb cpD�ϷJr_a9`iYb�ei~>+4h>}Om6z3tGUGiD]#BzR ky�xgۧ2Ue&'1JB/9uU;Hd sq0ϰ,*b}a*[40nbXy a +JL.A wx lqIL ^>,8}ꨑa {U =hp^ EΟT wA]Xڐ}⇁JKK`)LUMRd{iHEU=_1MsTb XZa)lk5ms&E*Q >pu$%5",K5YakƛV xԱ-Hر~cHJSXQB@Nq%4{ vMNVKY_V0feL_3 γ]V2q+5֦2Q?neF]/+HVLR4[%K$%+Hu2A28<Va2q+֦Y5le(63<0+cgP:UeK2>FneòC!)�wuv{ϙKn5L,L4q +8M>:B|TTnot`nEN~v{z& . $yu;0ؗFy3G;qt3`7v&;FL'3=L>g.Q.&vS0)m7 E_L>fY׌3nrc22o&sebvI4u!) с1ؗ}3J{huRci_9 dt@T:UԤJDd mGπ %9{)0wwR"^< tfpJSLA|Z9lWjW_HpD-Ks)RኜmY*cMlT3š,@lT<HHp'™X"ŸƉpHې*𬥂,`Jcb`-Z:�Z׿lٯG~*c6K?wDAP 0zoT<)g f n5*&HEim&p*$@>UĻ߂ES ő`tk0!Wt 0*Qb~3\΅+{@sHTc6Y3MG2Bf.-$szs㕅h\׈!g)| %#I̼7Lk&˃$,Tv0M%ʼnUĔqCQnS@oǚ`MkwL myte<cKB1`#fStl}5wAOM#y}!؛7ZY0.LDKM W:Bf6hH4!'I-~fU8R LfGF@D@+FzK%jS:3H=/r@iu,T^S]ս|D7cm.ݑ,cw[rwD w3Ij=q &kkf''ۺZraXD [G\Ic!nFט^ >4娅*](u8L-z*¦= 6XG8Y|'LmC]+@$GɒoIM!dlSHō)hɸ@E|\=jC_Z1tۣ<K.jR `3hZ%kLj-XD`&%I "$d/1sbT&,%>ԊD> >8څxt5-U U$llyB S4OH)aVUL\_}l.U*Ib)"NH;Y"c0PI@OHO!iYFo#<K0*`#ހHKjYM/YVhxSVR6fBQs`Xuc娮VBq‚Q$ 0ޅx0klF\a_+9h9BOF[΅͗Ep +7*x&zΟ[]ܺ/0q;LtHrX8L=HMa&neq2Y2KW 6e"L D 01a$k[Q1.{}&{abuy$ h�+u q0a&sZo PDCoHea"Ha>_a>w&;LDv\s|Dx&&XD&r ! nF&ԅI9L<5HYabzQ__aac<L \a"8uDHW]v,LW9 <v2a^f°YzL~&a؋I%$/ClΧHp@V @B/Ex}>q/30epf+^f=1r,L<en$|>&WILS'`p's u2 L"( p%glfKL#Y.+0n[* փz_B}39<O4 4{!u: ނq;ޣ뒘/Y ^]a#y{zN4n=)F^çСtf3p+È-}FG'0OA8iDOczڞj=qD6"^_ew21LǨ}_'`]O?N? 蟺k?u[hm>x?uK9/R?u]4@G?uKl4qPaLB-nE)H#DV ҈`.o-<#:'ZE2k]cGEPy+ӲgͷO'JrB|HӍtR\p` 9=:Fi;*983|K0 1mqyz4Yk򂭨4714q5/q;뛢I#WFl>Ƿ:G,:F[6Xƶ`"0Ԗޛco@MbȽ=`A]\j0jīf5 ̳k]X<2yYf|"= ,BoD{l+^ѽ5WpLӍ Z.7{ &ά ȵ*VpTMBD@kڝQzS`䴛 򰗚5]|Ky&9FVȬ+-l"ݪ[\R)L`U>c/fDHfr}KH~˖z1&͡$.n:Qx˔^H"v:$ MKaXKS8pd2~(0LvRZ48tR (ɏyC4o~!M(|�4G7Ys q`\ %Z[*h hu:_iihQ#Oó?"Vb@<U<B`[q`&ݬ~WB:2ռ8!V#gxޥޠTBI8$({2g�A%kHIC]7§MLsKLsC˂z&3OT/Rn=#/cXd~z m4fgbvyi 3QB39t6qoÃlxf{.Ã#�5w 80fx&Q {dÃQdǨ Ljk6<o Rx'!i cp< 6b&lx85$ q62 4.܋Ha䇵")xxҷm?aTtys ?~;Ô {хx> Sp�=:Fif^GA@g [I'[~PHqd|Pp.^è)(`[xGpfΐnO8&&G1/dg 0$Z`~EUu@NʒE9=:FifL kÀtZaf C.~\8kճ"X;qټl^ֺ5/.'⠛ØyYki˼u5E6/yqfd^־⚵~9< ^w\׬|1Bկ-d§jBMb^E7/kq FKjGcn5/x&myXpons%#X uyܺ=˸("V.-/Rudo\<P "L h5Ital[|Fjᦎi`&o@iL38=uLE*?l;^`uw@x!w6yI t,T8W ;0z 2l%a$tCB qS7N\502B5H󨦽�,-5!* ɓ* `MNM (D& GJI`O T#''4ұl)pɗ5WNeX@NM&& 'ߚaK`wwe8v) OHmB~ǘ,E ?j(h\<r~nVeٷ-<?{\=?70+sΌ0+A'2tCp5A߀-=ENN 4g˅(-s]hnAK>YiYnARU}*g~ K+xqs&?:x ', T'`;qx 8ױX$m7vcX)R,u lZ`D/ybꃻɝ&DW#'0&X{LhaӤu)2eDbRbnH7!5 �*P�q@I*$ª; 3ES@Sh߇+lo4lN0vbD['#*îmJ'|2`+.Qb<n 0x-gE:�KԻ]TǙ׻ M3?1.Z."0.U'Q~,*R76#9]`?9֜KH ^_j \ RM``dXݜa7'5 FFi$BaXCA{\%9D5? `H)I`$SaZ^!^U5QpFtx)K<ժzqa6zNz!6{118F<pTx8i&m$GR[ƣEVXCDR|ll 7)BL$IևFˁ ;`[-Ȋ.TTj^=V_k4yJKXi QŒ!ʭ Q!JdԼ!Jh\` Z%Ŷs QǤa'W/ >ʚ' OL} _ޅ1/d Ԟޣc&o@i>[G Znw2 Qbg,eiC4$6DV=+ѳ_S%yHxC.`#>~�_|w'?=Y` ;3J;l϶8ۙ)ӱGha% ժƄ/v!J !>UMpx \9~3/ßp0^63v -I *{,H`:Imej"CQ83J1&-L0QۊguxuT")|^H0W^ܼmQ\l ̒IS7H&ػ4e-( ;FN’61k:dϋ-_QLyGQ=ĆtD=4m6uρnM LoVdLq ~WlxWTuhFFbT~r=J̩HTT< d_slL� D~ [8ƃRò�nCjlyh %}&3F|޶x=7y*[qkt4RYY\15, JRyt=zU۰m8+!# osrXr$,laZ1VC`4F(RpDr?o{hjoݯ7I .ý2tHֲ+pVVdΕ-bB,ͼK]["Pփz`2d6tߑC!k; =4 8Ni_2C6E&ˊ[tmiI[F,d-:u. >^ BeXd6Hg_u=Z^дs yt[3ӆꜢgqEH}Y*QIpdt-l|ԶU7xc__udw4=gWN oAO֫ d%7C[lWV>/~+ S)q4īȌ�o!Zӊqqn]cQe2SRC1p¸<SΗrÒ?Q>b>w<TZDazXTx&eN_sI@f9d/ gŚ*ayYyS޷dy'mI{{rRQt,s50elyӲ=%!`l)T۞ϋW˪zD:D(pX ~W?OW M2coZ:hC۴.cf"Y)>t%v+)00L : å?2"~=M!Y݊+ֶ t  m \cyVReU܄^Wɗ\U+%%}JTiVm1JB/ֈ@mԐc"=j jRg}jHE5|=jH^ʁZ5oAi^_=Bkm:FZ6uZtM6m&"ykm|m~_k1*6&ۈO絩dks}=ZkKF\ksrM}m:צctX6W_EL-1#Xp֦ ~l#UlZ66},}m~kSǨhYL&31iSCLiZ&,FWµ_"71%w0* y.1ŷ.1= QCKS%SKLOM"$[LO+").q1uʃibj@VI<vk?J:ViVu]LmML 1Y1mgoendstream endobj 6 0 obj 11261 endobj 4 0 obj <</Type/Page/MediaBox [0 0 612 792] /Rotate 0/Parent 3 0 R /Resources<</ProcSet[/PDF /Text] /ExtGState 9 0 R /Font 10 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 11 0 R >> endobj 7 0 obj <</Type/ExtGState /OPM 1>>endobj 9 0 obj <</R7 7 0 R>> endobj 10 0 obj <</R8 8 0 R>> endobj 8 0 obj <</BaseFont/Helvetica/Type/Font /Subtype/Type1>> endobj 11 0 obj <</Type/Metadata /Subtype/XML/Length 1390>>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='uuid:a36ff6cd-3133-11ed-0000-bbb983f47083' xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 9.05'/> <rdf:Description rdf:about='uuid:a36ff6cd-3133-11ed-0000-bbb983f47083' xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2012-09-07T13:08:56-05:00</xmp:ModifyDate> <xmp:CreateDate>2012-09-07T13:08:56-05:00</xmp:CreateDate> <xmp:CreatorTool>paps version 0.6.7 by Dov Grobgeld</xmp:CreatorTool></rdf:Description> <rdf:Description rdf:about='uuid:a36ff6cd-3133-11ed-0000-bbb983f47083' xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='uuid:a36ff6cd-3133-11ed-0000-bbb983f47083'/> <rdf:Description rdf:about='uuid:a36ff6cd-3133-11ed-0000-bbb983f47083' xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>Financial/Invoices/Invoice20110510.txt</rdf:li></rdf:Alt></dc:title></rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end='w'?> endstream endobj 2 0 obj <</Producer(GPL Ghostscript 9.05) /CreationDate(D:20120907130856-05'00') /ModDate(D:20120907130856-05'00') /Title(Financial/Invoices/Invoice20110510.txt) /Creator(paps version 0.6.7 by Dov Grobgeld)>>endobj xref 0 12 0000000000 65535 f 0000011585 00000 n 0000013281 00000 n 0000011526 00000 n 0000011367 00000 n 0000000015 00000 n 0000011346 00000 n 0000011650 00000 n 0000011750 00000 n 0000011691 00000 n 0000011720 00000 n 0000011814 00000 n trailer << /Size 12 /Root 1 0 R /Info 2 0 R /ID [<559119227910D69F458B3069E10D2CCE><559119227910D69F458B3069E10D2CCE>] >> startxref 13496 %%EOF ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Financial/Invoices/Invoice20110510.txt����0000664�0000000�0000000�00000000102�12660234410�0032054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Invoice Date: May 10, 2011 Donation to the General Fund: $50.00 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/���������������������������������0000775�0000000�0000000�00000000000�12660234410�0025242�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Blah/����������������������������0000775�0000000�0000000�00000000000�12660234410�0026110�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/�������������������0000775�0000000�0000000�00000000000�12660234410�0027702�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting/�����������0000775�0000000�0000000�00000000000�12660234410�0031355�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AprilHostingReceipt.pdf�����������������������������������������������������������������������������0000664�0000000�0000000�00000006141�12660234410�0035712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting�������������������������������������������������������������������������������%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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������april-invoice.pdf�����������������������������������������������������������������������������������0000664�0000000�0000000�00000006121�12660234410�0034532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Blah/Expenses/hosting�������������������������������������������������������������������������������%PDF-1.4 %쏢 5 0 obj <</Length 6 0 R/Filter /FlateDecode>> stream xN0sX"mǎcnTyI p@eyI^#g{dl88MuJ5cÌTEǹ* U3MLi :Wf{>u=ۀj˾{x2]! fxtox%Y4~�)of/Ō ز*�.ҟ>BبB#B+8}/Ho؞Ql<+_X5̖M@8dҝm7TBDu?f#dh-ߵcXjUmK],\ꀒm cSB:\OS} fendstream endobj 6 0 obj 363 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 1388>>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='509a3d6e-313b-11ed-0000-dd67d504ebb3' xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 8.71'/> <rdf:Description rdf:about='509a3d6e-313b-11ed-0000-dd67d504ebb3' xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2012-09-07T15:03:53-04:00</xmp:ModifyDate> <xmp:CreateDate>2012-09-07T15:03:53-04:00</xmp:CreateDate> <xmp:CreatorTool>a2ps version 4.14</xmp:CreatorTool></rdf:Description> <rdf:Description rdf:about='509a3d6e-313b-11ed-0000-dd67d504ebb3' xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='509a3d6e-313b-11ed-0000-dd67d504ebb3'/> <rdf:Description rdf:about='509a3d6e-313b-11ed-0000-dd67d504ebb3' xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>cc</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:20120907150353-04'00') /ModDate(D:20120907150353-04'00') /Title(cc) /Author(Bradley M. Kuhn) /Creator(a2ps version 4.14)>>endobj xref 0 14 0000000000 65535 f 0000000686 00000 n 0000002533 00000 n 0000000627 00000 n 0000000467 00000 n 0000000015 00000 n 0000000448 00000 n 0000000751 00000 n 0000000936 00000 n 0000000872 00000 n 0000000998 00000 n 0000000792 00000 n 0000000822 00000 n 0000001068 00000 n trailer << /Size 14 /Root 1 0 R /Info 2 0 R /ID [<F133486CDA2B92BB1FEE9B7DBC4AD8B9><F133486CDA2B92BB1FEE9B7DBC4AD8B9>] >> startxref 2720 %%EOF �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/�����������������������������0000775�0000000�0000000�00000000000�12660234410�0025765�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/��������������������0000775�0000000�0000000�00000000000�12660234410�0027557�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting/������������0000775�0000000�0000000�00000000000�12660234410�0031232�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������AprilHostingReceipt.pdf�����������������������������������������������������������������������������0000664�0000000�0000000�00000034735�12660234410�0035601�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting��������������������������������������������������������������������������������%PDF-1.4 %쏢 5 0 obj <</Length 6 0 R/Filter /FlateDecode>> stream xu}Ke;]?GMs<�5�FƃXk#mneGJ("?/[`T)ׇ??5>5Kϟ6g\6ǧJϚ_ϳgjӟyqϟ^4G?!Qz&^gV4pI 5nSZb}VaޅG?w� 8$uNs)4ZgpU>\&i-/^̾41kmp{ԗKs3zЄ)Y:kgi&%;NRxWj%D#NrK`M~X .i2ȝXF Y1LA{''?}W dn5wL?J(vD7|]}߹mɿ1}.u&?}n8/{ʭMd}/ P&s}[3>V^wp({Uv J}<V|w8w�%ZsY{o5K)K 9N j0wOvwͭrPuy$`% I3,]ւ1d>XSVwQ(H!r^iDWT3c'wONHUkB*88H OMaw`I5'X*vx!-c)N> u@B Ot\>_<r ]3)]~% Ai%l[:tU3lc0siY x3;1{cGe-MdOcb?h=q<'ay.W”Q%M8B.3z%BOtdg(4Pw„:g`xwٕhC#@J!#RIM&bPL`|V�@T)"uPM Kp++\8UDWDITF69wߨe3NZ"C̀]%::(eE1@c.-p/{$`$]rE]].y]3u|fM]ނ}6:o[P  X+8|ϛ#0_ހҺk_w3Q (Mw n J`[":IYlƃb/CԻ=Kriut;rK]} q*VwM6 .'DP՚Fa֘]wN}}BORp)$LslhNXkF=z%.P7$2a 1pU!fA44t `qz]E9]'f_|^*.0k(Ծ"S K*tjP@B 4N \gI 8|a X:DÒiWP`4?' mv'.=Nr'J.�1EJZ0#R Co_gXA6,i5͗ҚX�( 0pwREBnەE^O'|�`u=T¦.?3 ,2a ë}sќ[Q~Yc=wc881f+iH 6) 8ˮJ GSS5?q[#CdPݷ#TZSe=aLˍ`</hc XH?a:̓ j–|a>{y!JP-j;΢[#0.\4Ӟ ʧNX_%tݘPKn\"}{L>_u80V(BnterV:l0V|0\9!A7OzW#6KP4sF:h9B(G`6> Cx@=W$d|1%êFhA4˲i/k& >!Dꪶ `9Vk}2fȿ+vO UoU+U7^(O'zVhh^CJvBAcrkyLEs`]E ч1Dy`|:8[j1J`t[,1Vt:IǕtoYWp]cKAUH}ˀ45n['ŀq YF.^5gx; {� 7Cɺ4ԓ&an؂X `G,>sY2^瞐6 ^tY F</ѷcEʖ;m[kuG[l|_ݶ,RZErUʵs+& ,rW3ijLm$ 9Z}E,D=}R #N杢FU"0 +'П/@E0xo & 3&<sK$jFCZ0]:m7PVեbq+}B-:]E60gU'IUa.f5y |,D܃VNF x&4+`RrDr13]+3P P/PXpGI 8mNcs%"ÀQx0E\frQ{ 6sn+R•C8) ./xd.k*Ē;j 5MrWD5斑H{.uTCO %ͷLCb뀧iϕ`PLF<Di,Q1|և$Pav̒Q+,RH CF%:}fHqqDsNaA6?oS ~ nJ+_ꔢk>S.~]%]8 D^cW[KRajprQhm'QǦ:20r'+~ \t /OSOJGRv;[I&}P}N�5[EA;*lUQX)<sb˧TbW`?BtmZx1SsUė4ࠝIU&,&*`VӴMaǠIzJr})Ti`rSs[0j}?GQlJI.\`MoYCQB%Ȇme"$x<+AobIH68#�5Tj|8MIj2I.=Q[8%'X';ht')#ŻSaBvdEԬUZt-Qs@tan;-!~V@- ,=Քhk!W-Holk@- Fb~5g1:VQM4E j _a{pzz)2lV Ey<RzQqcv+J\ZÅZ$F J4Xpճ%0قP'v1[]u65"qqƁ,035q/-mj '_~wk'/̢Yc\?Q:$v;B..:0sUָDӷJfm3`40BU DGWڡ&J{a-`fPbHXN9;F-2:]]f&֡8 . 4yu.5Bً,7R<[ءQwG'Y1ʃh!}$$j/;,.ڔ`ΎQWp<j>`c8zu4Igӱ.1Je9g:0戎Zck &u]fT;B sA'bq�B!e PXh%o<!A-@=I!a_pqb!_>Fg}Z q^u ]7/ѹ#&<EÉv9[BKsDt>JOŰFBIJ8/jRL5dX;y\T) ֙j";VMXK@: >z;jdb[J/O {pg2C p6Ve(266`eC lPr %Ǩ0l(PZP(JCioCi026 y~Js %H0 `PBl(2||`% l(aoCW^ü.fͺ![~ru/++sLy% .<rׅ;̋]lAX=EE=BMF.L|yqRWY[#t5@CX[ʬ\pG%,瓅T"`�eK"-+f½.x8j` HoSޫ{ Ayk2 jםGS}]XWם%z6) aׅ\Jׅl#*)Q_;^Nrg} wWka_w"K2q_/vѓ݋z~d+5{�__<#›%a S3{StS55@;6__e~1Cy7T~Q`p  鿋pݐ00ʫ%D\X4}7-nV?ru&kU !F_t.w a|.Ѻ]`uFZ}aPOUFݚt;u-fktE tƊ'D}9׼356S1jly jYfȯ0!w .U)z%A6ߛ3np}qNݿo%8a8Q8 qk0\T\ZWƩO/ƹ"Dh&Ndy /,vb�mzxB8љ0Nq#-cx881<vbgfq 6cSL:Z/딦UDt+wӵT5Y+:OI%I"PBBa6Juоpo_ f֫TPL[r .D<E͢"BdK\ @UM1+Hpӂw{d'ꉐ0V@u9%:X x<b/Z x:4po%*c}ɩ%i ).F(}'y2 bb&6YhAhb:%U#Cy *"NL;ZSDhΜa<GGFA5M8\=J.4\BH m6fӷ%>o@gŴIEvKN :KLh@L2%$ Bb.o6w<<-ay&;`˚ģ˻;-4jm |҈C1bwXv, 1LԺ`&cS$-H1x@]rlpC:_󨝉S72,#y_C{ k^ag/rZK#Nm@ l\ea ^a_,Y C;U䎽�2푅;A{1{ {%83 w=�x; M*; t3 hxA t0&ȱze}; CIg04wF!BC BbP!+y`^(@19Fah%<# h K@{-yŞ`P8`CJ7:5\AGzGSR%̀Ro'hМ!4pP f<߄my7uR xRIT�wxa:+$ ku6j-$ rOQ {J`ȿidCPuX^pK;9?4l;6;Nм@h.Nм&[m#+ DvtƙTn BOd (aA0~ ʯ^ʹC=!_B&$+fl;6dع>\FC6nNo{:g1xcP mCY| 0$Y8nC弮'$xCH>5xRш\vq`u@72^v"b_uWã('ZZ$=hSLG s9>_[ޮ9e Aɗ�:Dp-80aR/oZ[xkb~.('jӢkFHdC# 3ma R{}k6F,``򈵊G Xs (ޅI_WԋމTV3es�3T+,j% hHtVU8SW6:ٳy%-l}FC5=4P}5,1_ 52 CbEUi3@GRSk+: Aߗ!i,%xs^+|T7jl+Y2Ѕ)P^,MU, <,mbda[6qa\s)00Ԛ1f$Üt5tki`Zm%JK $#;CHJfąuc p�/L4U2{/~ԠbsF*)OpY{[Iil2c;\-E0^+cK:[IJ 0Q0 ATp iR!8 .8- E틍Z~6#ЦC[=?q P.FALpx䃈޾ϣ )ICh|ī+uo%-hT|Cxףr琣}To%>&xNk0`ݠ*rΰaSj4P{p_O Ԁ(<--$"a*o@Tj@}fΞri/ p+Z恢B/ 0|O`*{9pVG a[0K-o\X}f| هJ9Cw@cVDj5ajtJe"b#z?t5HCS%K]q)Zn檴0lb0^IarH@\]Մ]H4vPH-haϔ&#I^)k- o7D9lĢX˔0|eId*Ho/[QKcpoB:xw̅ɀ Y#*` DD хP<>)9|"腩= mq.`և'7!up]"`50BZ >^ acN\;d16*Re-s>%M RO-)32bDV2atˌ#_0iU 7G:=Xi h`ad9#6#|fުpz F c ]TДL޷3U8Ib\sI)0fn:L- !Ô)IWn&~$s/SWL<ڀc$%$<;-0޿XY_ÿyO`pAJM<h =&0;<AeO`,hCTtN(1Xu`_DhSDs$ۯ ̄@/H.f,_YJ?iCf8m ^[uRLi6a <'z7UDup3ŐlKAϵ2UeXγ͵fPh.mVUVɴLezN##rV.rLV*&|!B(;%aH:Z]NSfO{2qʌ_LvN\ּO\]SwSS#AZ>eS0v/2>M~ʰW:e�N^2Sf:ebضq/OޚS8e# ))+)?S<N)XtʀAާ̘uʌ)2S+kV]uN\uSf'3B)ד)cp2S1zLXv8e"` S(]_cT|LIR`tR0y/2:KƗLYcA\>eS1~8F%ig(cDN\ SZֹSh3O_#z[A;K8hv}+#:ŭ04yvô%/DE1YlF2QU$N11Do̓-W 1˜)W 3 6^" `;p֤֥#.GfQTz-0[%)2"fyR^d^d9sZdf)"Y~-abh +/23n]dE|w{yWzPR"QсbXf?8x.ᗞ6bhinM'1ì�R6pIzXtS]NGFޠ^3xalH QP]s񑵙B^(:^ڡ+BS5A[ ϜoxeL¨Q\݅ WFa9/M5ʣ1DY1<戜Lcb:?xQ ]vxl.Gc l1[@Tf\19sx9qw /zE#qOzX !k:I{FZx|^ц<?Ts8I\=C~uWI:2kK̢-EgnE D"6#*ͧt$ e<Ҥ|:Џ XaG8^JͭlF(l;߾1D[L sQ~T*UFOI,HXUb$!.4S*ݾ}}kcx7f]%;M�v̏6\h_ iXG#͵cmlFXiݖjkXX 0XR_,͝3}h+THew$c YfY|wM& c A9tV`:2W0,p�$P`nS:-Ʀ1E\Ș:qu ~iH%~K! (`$BJf0?F'آ,3ˌEކ.Dnń?@�XWdEOJK j~KW 1́y"/j`:QJ8kΟ$1C0j/ ;[+%j|WW7'$qyO4ԥP(@gU98J(Jx6s<]^5ݽ`Q_apW+_`XWi\@iRŒD0>0BIU5zUc^Ph;jo_`Ԗ'0ѩs2Ѱ 'XmTrj{᎗44a1U.7&*$ )$8̗S|ttWb/0ml;cpU@̒" z>S 𭁻D 4y2M a#XfHEPL"!Ze3,,n{fh/_iBFJ] PD+lD$%/Ȁt&B%hZx?[ y'[�BADng( u ڈ~`2 mʓHsii S�xskqW'X� 6ǝxȁ|.Ru\C&#.Ǻ6;w@0l|c\">Nvʛ2'TDDmN &T͕)#c~X?H?<)?G?ޓJ`͂p0c 2CHL>Bv=�c-㛻*0e^FR+cJ71~RVM Y~㰺E\XfTj%pbtU ޒOC"˔~~׀U8y~E2\Ke|M-[I o%|)TcWɂ`Jk"Y ]2)ɓK:nMa+3O{M9?᥵$0?w8ś8�g#"TK*);as{Uް + hUcš+=֡c$oA(.ݏæmJ-Qn7@\4ްYNbhŚs; -ޅi0  '>浢]_x Z⻓Dcn1\Ӫ +s> yZO˲hvُZ%!0S J!222ME=  C9 x a1T>̭8|g> Td1&RgXGh-AhofN*�G(G8M^B-8\%'a !|AS _c9~Hx~<276!Bt{j"gn'v� Q7\7/U 1<EC>#! aw@zhEULX6,Y,z6>u~s!Z~|y'w4.; ݉bG]w+m)%[\"N`M%?B;zb3aP.МhfcӝCz?>pVC wv) År-3s@h@^v-ge~OhT7/6rP"2N鮌YT3+#_Svu3͂<]6>DMwyUHo^*P w—p"z%^w> ^BGLItzZ/ӤFW y>VXJd¯<jR{aS/1tA{ӏ)ܺ>(*t⭵!!J\P~xC7FoaB0RujR)sPJ0>5`0d >֊7xU0KEo Vw߫]W6,>Zj[u_WVG#GjGEo`0zҎwl�/Cy8Cय़Q5[S q-)RޞDN(S%oK?&,K3u[؃+ Iw|.M \IjwU"lQt%(4߭+#ۘ)_qw#7>v^)TޠSL[ Kg@J<1zA W ysHEyԇi0W1)r-.N f4Q`mg(b#(E{rI.@@-"U\2i[` lō_7v)dB v XC45]bHx`a5#OqP҃!4.ٖ8`۵{-M]fEhUQb,ᅱFt"bF=$iÑ.fIaѻjHwi#ZP u5+$ctoZm>|coZ7ߛemL٧(GM_L 7oY 75!6lYFm>%ۛO?[ j.FjZ5loZ6fMtnk껨['+`{k?$2MѪNMf#`ߛFewΦQ6%ͦTaljpaM|m`SV(ͦ1Vͦ|;7cs^lU2Xy/6rl<Y氳cM`mZP$n3eMuƦl*lljlljp{i _endstream endobj 6 0 obj 12154 endobj 4 0 obj <</Type/Page/MediaBox [0 0 612 792] /Rotate 0/Parent 3 0 R /Resources<</ProcSet[/PDF /Text] /ExtGState 9 0 R /Font 10 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 11 0 R >> endobj 7 0 obj <</Type/ExtGState /OPM 1>>endobj 9 0 obj <</R7 7 0 R>> endobj 10 0 obj <</R8 8 0 R>> endobj 8 0 obj <</BaseFont/Helvetica/Type/Font /Subtype/Type1>> endobj 11 0 obj <</Type/Metadata /Subtype/XML/Length 1405>>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='uuid:863b284c-3133-11ed-0000-1e4000539d26' xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 9.05'/> <rdf:Description rdf:about='uuid:863b284c-3133-11ed-0000-1e4000539d26' xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2012-09-07T13:08:07-05:00</xmp:ModifyDate> <xmp:CreateDate>2012-09-07T13:08:07-05:00</xmp:CreateDate> <xmp:CreatorTool>paps version 0.6.7 by Dov Grobgeld</xmp:CreatorTool></rdf:Description> <rdf:Description rdf:about='uuid:863b284c-3133-11ed-0000-1e4000539d26' xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='uuid:863b284c-3133-11ed-0000-1e4000539d26'/> <rdf:Description rdf:about='uuid:863b284c-3133-11ed-0000-1e4000539d26' xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>Projects/Foo/Expenses/hosting/AprilHostingReceipt.txt</rdf:li></rdf:Alt></dc:title></rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end='w'?> endstream endobj 2 0 obj <</Producer(GPL Ghostscript 9.05) /CreationDate(D:20120907130807-05'00') /ModDate(D:20120907130807-05'00') /Title(Projects/Foo/Expenses/hosting/AprilHostingReceipt.txt) /Creator(paps version 0.6.7 by Dov Grobgeld)>>endobj xref 0 12 0000000000 65535 f 0000012478 00000 n 0000014189 00000 n 0000012419 00000 n 0000012260 00000 n 0000000015 00000 n 0000012239 00000 n 0000012543 00000 n 0000012643 00000 n 0000012584 00000 n 0000012613 00000 n 0000012707 00000 n trailer << /Size 12 /Root 1 0 R /Info 2 0 R /ID [<2ABAF442455C64448449135E321FFBF6><2ABAF442455C64448449135E321FFBF6>] >> startxref 14419 %%EOF �����������������������������������AprilHostingReceipt.txt�����������������������������������������������������������������������������0000664�0000000�0000000�00000000102�12660234410�0035624�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/Expenses/hosting��������������������������������������������������������������������������������Baz Hosting Services, LLC Date: April 20, 2011 Charge: $250.00 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/Invoices/��������������������0000775�0000000�0000000�00000000000�12660234410�0027544�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/Invoices/Invoice20100101.pdf�0000664�0000000�0000000�00000035116�12660234410�0032446�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������%PDF-1.4 %äüöß 2 0 obj <</Length 3 0 R/Filter/FlateDecode>> stream x}RKk0 W<HO+` Mv+vqIVRJHMO$,1M}>n^{iX֏B <ʅbETX(DͧuS[-8!\/5"[ϴi=|*T[\jJ\{`Aw3y!r1 $dLT@lzВ :)"}@R5y &,m �vԜIܾzx�㐮XgZ&< m/y<6h\\\@sd_[bLn7^~9 endstream endobj 3 0 obj 316 endobj 5 0 obj <</Length 6 0 R/Filter/FlateDecode/Length1 17880>> stream xݻy|TǕ(\nՒFZ l!aIXR I FB%c;81xL< #yϱM^' ɐd9Č V띺bq{ԩSuNUQb&3e}ij]Ӂe@um|Gq?>$DټM}bJarS%!7! Rw\eywʦEb6,?m|xsXF~;Y,1YGw'ƧG<O ~brt}c4�eFP˔ No0M;. ࢐&?45MF&.vx}7WQ3's49H~Nm$AN\}J>5=A`}.I#O<yzI;=8ob .q}{i6o�YE/b)^C+HΑa�9OJ/#:BX4M1;Ju/YE'ȶZ|aF. *rRgӗ({ %;(;=Ȗ]iZ׹6斛WwZookmY|kZxSC}]mʊ°Zj1 zV#0 5Ԗ )0rhC!�nI*YFJ)7}RP*W)A ,%K@굖P A`K77P,XE5gKK @kmזwd\Z1j,/#'&MC'8*@[o:I»MpH* LYC-jYLiWt*:98Y63"٘GB#CZߗGR%TsPTY5\;:qKHib( ],FH8؆ݿ-h۟?tf~fc( 4OI[Tۣ)1n ّrГ!9 4VMPi0?pF!Yۓ)F)TFzS4kfjݼffjdgc]^9jEJlt;~ ;쁆^6Z92Hi Q-RxZ~]avG!l8Pk2ٵ%RHfzRJ PvZOVUb$NX:}DZ~u>ZMR)ζJU-!p^=/Kd m \W{F6IM_0zF{BC \j)c]cm_O}v N~MǗaK.=zPDD ҅Q*/K=# 8TIu%K70"MˋgE f2ՁlBϕ_baFEq]5 zC[)%eQU\uPNY&Wf-^v|Dʅ~}c~<eHp+S/aSw?ϡ!ĸ𽼅o#CzhArZ^^l<yYpᮞSޓ2 DQc99ubA^VQk�Qq34pqB8~,lAn L^Ɖ Cb'j)chtyZߜk9^+$(/{cN ƑtDӍѯTRN_>ռ5}J5|p|` ex2E[BW!ߍow:ȚÚA<�h&,3Cb鲌Xh X` zrYϾxs"ʭ-+I o6U,.laeL[T^[.X(L, Kr̒Z3E0ih4_19ЏW$c$^;.- {:{QMЭNˉ4DWS[SKTy1`b#!D?A)5qAzh0mbJ0aW\bXh80L\HU<Wf@aτ|ѿ(;8m)�Z?rE?awcDԈ(Zm^FA:Q%;wj?1.n{F%EV_Znk'[]۝[Zؓ}0(8 1w[^=z0%sad- >xq ֏i7eiIݙ /qmg"рeEFt.HSoз3bR0@%tXqMG"-"Zf<'"sX/pZ<')c"ؚEqQ U"D8tF'DFod,ah GJ9߱~,bAW*#"粷Uk7@fPoU{�pݨXnz�|2{v ןɣTczj(UaJP$H UDBP%%\ҲUg0% d*k".ŵA%J}/KpUᢚq.ZTX6Z-TPܮQv_?mY*Y/IU+GrIƞ [[qhV''O+*y#{xH4nrE\õuu%Ry׷\ﹴKih 7?;J8�W^W9>1Ygr67qg* nIU;W(sY|k;{A$ET\`7~9="/"Օ#>׷\;"a[Pw:JH Ĕc%K.hJJM4?~#5$\6hZ 쿄fpzu*28a+&䱇*c;{K%+kEc ˆ[C^ԾwW̼sNsϹs+Gﳛ56|~RJG\LN* }Ԑ)y*G&AlX2VgkŪ8+ҊxK D8 $v=d hW-\Ƚ r-|�.n1ceHg;ؐ|/|+ܤ6dQ/ꄧ+߳z=+3xEd.� /՛y rQxE2*rR>&_% Va@|!:awA:vG\51MM dNetAǯ~uஇ k+_λ#OܷkplNq7}lX }/lqߙJ_vfwja78:ԐJ(:r!1=;L)7kpP-ыDhn||猷qxZvpF/='7t0/ѵkС<o9;sW'FSJEsL s|,튨Q4 1MJkt3F-LD侦:\5 a&Sg`ߒL6T 0!w1)0>WO 6/m5Ac޳_z^x /zVe)/</؈/>}[e^8^8fl ؃L"y&5^ ԕq{+p+=^Hrf/- VPov8"c.PUT\R6/,4 y˪xe8̑ 3#yr,1YbI#sA@ⶊB.xT\-_�a*qZ2 /�f¾->GM+f1 Z W3<ξ><%%-%]%L mqR"Хo�ǼT?Jtɹ7KXU PN(aJR%T%q_t#!#K8Xw)Ao gvT50`I M#(u2< ysNz3ʷy˂Ng-rkjE69 ˔ĴAma]"cuvzf^Kݞ/x2APԠa#Ap ,pl;C>F>Ӿ>zK|-X30Ҟ1oۻ9.m8S||UxRYe!;;  6js^7ikul: \4G j̊0_]1{*++yjwqfv}bDy ubץnCYʣZ܏14䦕EgӍ靹5nW %%3Jͦ]I=j=}Z;H!hǯFX T!Uk4$r΄DF1Z5E&<j4Fgß t.&N!-q=۟N`B_pKqѽ{Buzl2af0/{ᵱCENox f n\e7}J=BD d+%l ",t&&UhMgM)a:fJfMC\61S6U ͸ Ƶ j#IU7&0cƣS ͜)mM?a=2e2\Yr s(q^0ڙ$y 2Y]~+?@μvx0CA>g2o= -!J~0N^yLKf2xAf&|EO__ˀOex^GdA*&fLߒ+$pT2#gO%p7EgjQ;6?e@?m#r!3 o!ouPL IlL_4otD~Nf+e%2fᘎ({ezyL |Q/ߗ#jyLoQ۟_sYd蒡MH Ç|Nϩ)o'eDnz(|f~FIߗ|Z ,9%U e6)վqR�Tpq\\櫲 OȠ2ġN3!9%FʬٜG<U< OÈG`4ܔ|pTSȯg+9Eʊ&ZG +`DkךyB݃=Oqduu%9~dpu5qӿ57R{_3}Y4ɃIUkL"STH<m!/sp$[Zs-rWu*)7$>q`aFy5)lhc`O!)0Ƹ!FYQ㥿tpzkn/8e?D-Brҵ ;1G[h[ɨ5+㇀CR b(>V|]А<MР9lj}//p?;+IakL pi"HxS?{mUus{|gss/4g t_9G[܃at< Vy<}@! ~Ỉs#3?S* XEk:k=o`\z&'u`qJsD[a3XheD|%;%\"CE7zb,Jܔ(җwqnYNⲂL|%܋s$3:M;jnN=ԎkhCz%Bjypo.%mU.L~m6  :I@㮻_oj2PLD#noEfB,(A{PQl<qay a6Wc{m ˝W~ٟ ZsK<;.Ÿ_Z%BYsW�q>C�Ţϱz#`\>!n'&v3VיєVoqQܖ~,lɳPsaj610 ݠa]5n_GCFc1@_AF4:D!6ZBit݉pN;0s܅}WX>5W65{7$B(<p2wN8g4=ˋK,B@ ˕rP3((=h$|Ba8YnV*#'3S.Sf\5K27!'ZOϟ$Z./h{9bg^97L[\Õ/׻ V1?D*^zbv =a"cbzXpN#z1^jpnAK)KgK/ J/R\xe)(ҙC4{D}F"r�]yD1E9\.hƿ~á?`<~:N�KzͱZǞG}d-[>``]G�8;0-%Jpk!<J1:&[<QfX�0cćNgm`=(vWziƋ֠(,\IZMxֽ(AD5*AO<n'nm]=qu$2#e{|g:E7Ԯ:h ֗Vm /_v=2\;sp^o8SڱyilCH҃xiku^{kVw@ΊD,k\xɭJ^}k|hmg WE].a" 0?s qds5iTD4c#' ,*jp%qV$w?m 7.wMn']s{-7j]6c|{Qݥ1E4@ب e݄;uO2 HLmQ,Ceٲ eZ-rM,u81(&RѨY/VM5 ժT*g;{� fnϞn!HM[sjۢ?3w2ڒmj8W7X fGPSN9"g^&NlAUkѢYm�ŬNhi]:VX4.AD<DQxF.Q0傋̹qbô huuԺֽ[)A XIV0"ՋZ` A#dUۤ? #;"3*5qc)BbLmDA!,Ȁtwͽrz}p7tE<W2Q/;KٷB \jj5&,K>\B5KKK+RDQDzKT _)g(# ].dB.G;3\г7<ԣr=DJoHTݙ(&cAvmPDϣgv3^RRӮuqR3%1p@?]ع}6cp c1]@b cbp<MHL#@cV1z.Gb/1,ha *>tf=vP &c0D @a!E_|?1y^8m>$UGHպ6v*Fg>/3'>1>�v$r]*XmҘ#FQf/q1c ~Gu1F*㈪.>|dS{rr9K5QX;lDf-1&.h<�<C c[c4aMUر9RTʆ(؇1aO޴T8ϳ1b,R1-zmR 6g\'Z>봈<^cGԓYO4rc^z=_6^H9D}(:L[B|n,/HO 0 ȻKnݙ,iݕ~;m?^~;q0g% 놗zFPZYHWz Iף~6YCnfzD]&1?7.fBU~aO')P2_z ]W0۴L +ϾmV -h;2f6HS1FSݱ={ظj访o!;}~kN(�^H- <G?B&̏j^%uD!+*NKr=,9_̡`#Ve]-VlUqBl0�JI Xh6RiazKnso *f1 >TU  |Bl[} na-qw{T~}UͽLO"X(/7_L]#J50;< bF</#_#=\tj7mQkM+G=c𳈺�ԧXSAD-fWSc=ٗ-n<չxlYXUWtxjغ;FʱrStQZ+#u}k=kKqi=T~SbKݢ6u-8`uխ+t,s̵1>{~N5~؛D.xzHs)s̶8 BОc!Me9 τΆ~BXx('ރ-G=HBHYNp]t JgB?2k;Hd s=CQOP�n.xPBkf6nss }O^},wݝ~>|!%mzh]m (15ˊz5Q7IZ%- F([:3 S҂9VDw<v@YU{LyrMi"E1Ybj17 L&V.^ʤTf?,DMaIUlZGrh޶pgRZ-fZ㢮nRؿqyhi˭nm)X7rܾ֞zZKA<Qb=WzYY~AҾHKi~ON PuVk߄nUt۠!:'fO!`)3k4u6�#iaSWADGG(tzbڬ贴u.=NgohVk׃̦>z? PuiD_GE_">-ApwH}vާO^폫38#6"'@ܢ;fz9qEdXu39ZH0[4/YQOfUy?:}b$"gՓ^ꂿY}aг)g}:}Hz=? ؗ#>ԬFY_/!@sKr+ pWTe r FU+FHm*7&tMI+sc*N{S1te Fh 2xDU"5eBn%~s|7bjz?t%X8y?Nn~\Vjbw`Yٲ+7MKmTDzPLbֶEM{nj؝a9${o[kG( uDo*l@+ג9=6EPNl=@[4CA3`g2V-!06e6[(tZm־ �M�t"syY0jl6<"3LKW$_HP(vI-=-s* 6I%A+H ~aDSYZ"HLJ%xKg(2(@ $A?1{R+W9/Ʊ1zJHx/z<vw @+G)}J6aj/Kp0!ouQҍHT0R)e+$PD҆^%tXb.iZMI6qy>)"uHL˔Pq55X0U9[,Vh"A0Y}5\fEi͉/1Y䇶hD}bET#?# oDBIh6`'n|$lA݃)<O<:˩W tѬsWդ֤[vR=hr ~gO<'a~<N"ǘ!?&t$+&5D1#g5_71<f`s9dN_]We jbNȾ{+mv \-5yKr7ι9u:wI^2MASA?^_f6ѯ|4v`z>_m~?U-DAr ~4xY%X^p ޽HW 4If=:#G#o0EYJW#tr P m*vj/ oi>ޯԝח4Ub.5?k7k5e[mkd@T7y~T0%:ŒPX@,,%6YXG&_zO6+D pc+; [x [I# XYH~= SbeaFjYX fga-)`+|6ga=)6da&R/<6YBҚh-cǦ M ,>2:X1>91>94=6"l۶-Sѩ]#6fF'6un޹mh*')>Y^?:9 +}tl*0chrk`|S. >\<65=:ȱu@Նk6mUãCH<>zɱaTŧ)`CӣS۷LOOTY{,0V QM51:2:5y;]emS\-(uoOo=49ڹ8Ҏ۱;;26%p4j5`]m1*~I!Laَd; vo)4wvWYU̵NŌMd'B_~ M]YB?uLnZCVčc*s\mYJl;mRsVyg(THNJ-޶T]qZF9s<FoGmO#Q-6=ﮀuv}ެՙu[ A%٭U ͍|+Tm7MZ?Pyށk[] uOMk~Jm1#RjaPQ8,_N-;۳xk>o s:V3exaDŽّZ>k:Uzŧ\' h{.s=W qf>8%.L׼? ﳪ>%RRĥcFL?_2n/twaiݙwS2D7 &l g39_]}mq͋{^dɯ+Qzi=i*O?#K45G,'`xhCvE%R7ӭn-Wec-9hC_:58X m̯f1vZ^H\NP53 2Z5jf[V⏷m7~vO mgۘVR٦n)n 6o6۠mt:#azZtwv )x8^}))ݷ$=x,HUI%{;R#(A@?)SS$;Dv"j`*$j)25^!S?6Rqhj*gG endstream endobj 6 0 obj 12236 endobj 7 0 obj <</Type/FontDescriptor/FontName/BAAAAA+LiberationSerif /Flags 4 /FontBBox[-176 -303 1005 981]/ItalicAngle 0 /Ascent 891 /Descent -216 /CapHeight 981 /StemV 80 /FontFile2 5 0 R>> endobj 8 0 obj <</Length 388/Filter/FlateDecode>> stream x]n0 <EݡUBH--hl@F@x6ۤ@_/sea]oZ xnmmD=4z*ӍYonm^*vvƒlonbQn\ߧ 0<-tS3=7Z/ޥEc*zla 17 EVrgc]tArNJ8 i|3S|"1# ġB>2Y"q|#N%0쟢d#g$G)?:#g<"S db?^0W6WmU<)2w{E|fßnkjVo?OY}H endstream endobj 9 0 obj <</Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSerif /FirstChar 0 /LastChar 37 /Widths[365 333 500 500 500 277 443 443 250 500 500 500 610 277 722 500 277 500 250 722 333 610 889 666 500 443 277 500 722 777 500 500 556 389 500 556 943 250 ] /FontDescriptor 7 0 R /ToUnicode 8 0 R >> endobj 10 0 obj <</F1 9 0 R >> endobj 11 0 obj <</Font 10 0 R /ProcSet[/PDF/Text] >> endobj 1 0 obj <</Type/Page/Parent 4 0 R/Resources 11 0 R/MediaBox[0 0 612 792]/Group<</S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>> endobj 4 0 obj <</Type/Pages /Resources 11 0 R /MediaBox[ 0 0 612 792 ] /Kids[ 1 0 R ] /Count 1>> endobj 12 0 obj <</Type/Catalog/Pages 4 0 R /OpenAction[1 0 R /XYZ null null 0] /ViewerPreferences<</FitWindow true >> /Lang(en-US) >> endobj 13 0 obj <</Author<FEFF0042007200610064006C006500790020004B00750068006E> /Creator<FEFF005700720069007400650072> /Producer<FEFF004F00700065006E004F00660066006900630065002E006F0072006700200033002E0032> /CreationDate(D:20120907132418-04'00')>> endobj xref 0 14 0000000000 65535 f 0000013815 00000 n 0000000019 00000 n 0000000406 00000 n 0000013958 00000 n 0000000426 00000 n 0000012747 00000 n 0000012769 00000 n 0000012963 00000 n 0000013420 00000 n 0000013728 00000 n 0000013760 00000 n 0000014057 00000 n 0000014193 00000 n trailer <</Size 14/Root 12 0 R /Info 13 0 R /ID [ <DF7630076C26DABFE2912876A9D5E12B> <DF7630076C26DABFE2912876A9D5E12B> ] /DocChecksum /0C1E7C3FAE114F1E1399EB1587F95B50 >> startxref 14442 %%EOF ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/Projects/Foo/earmark-record.txt�����������0000664�0000000�0000000�00000000076�12660234410�0031427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������I, Another J. Donor, would like $400 to be earmarked for Foo! ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/non-profit-test-data.ledger���������������0000664�0000000�0000000�00000001644�12660234410�0030621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 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 ��������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/non-profit-test-data_MANIFEST�������������0000664�0000000�0000000�00000000562�12660234410�0030644�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������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 ����������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/non-profit-test-data_chart-of-accounts.csv0000664�0000000�0000000�00000000256�12660234410�0033550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"CHART OF ACCOUNTS","BEGINNING:","2012/03/01","ENDING:","2012/02/29" "Assets:Checking" "Income:Donation" "Income:Foo:Donation" "Expenses:Blah:Hosting" "Expenses:Foo:Hosting" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/tests/non-profit-test-data_general-ledger.ods���0000664�0000000�0000000�00000013212�12660234410�0033073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PK����R$BJÍ��8�����meta.xmlN  ؖHw&ޙ8YE4@-V51Ahq~4-)JKF w8h \9F|Xe>H%j :p[!(kN~Nhm_Kc 9Ni;_HR'TəkZo$@X g#9E=>N֔nPl<}]V?,*:/F?#>;iNsvU*QG g*&v >:cmuԕ4c%3ZS- ֔m0en5\J4MJ5 3۪?>$qn@um4, X>e{pix-,u'U;{pCbcoߺJ._/OPK����R$Bl9,���.������mimetype � l&чRIk8sp[x\V אM5PK����R$B�����������'���Configurations2/accelerator/current.xml�PK����R$B?%��������META-INF/manifest.xmlj0 }{maP`l58Ҽ–f_K2 |D-^h�mtZnVX}=ڇ]jQ2hȓB3�)*&@m�Yԫ},p޴<&¤5\cvgtK8:MB D$u Y{,yN<1?ߖ- ܇bp7uDMl�h<<)0cvnmPK����R$B_?���� ���content.xml]r8}pa&IU22LB-$|s$*E:nA_#1%-m8>&Öy߻/>�C!"‚ߐք{hˌ({{z4B$^2W*)(m(ze-藟9Q.Z L+]Ԣ5~52a^@`2GBDmO&AvONNdt Nm 5݆k!,>[DⰏX�1 V6{mC FkrrlzV ,,;ҝ d8*}vўR: 4tC;}]ОV}°@A�᪠I=ז2 _cд2׺ G(3e d-mκfqn$> H*7*-Fy4sO-QWA'Fe8 fa?7zsZ+fM!"aY|96F�Viq8ueГ&n1:1vS1YL`3+%!<i'ƀz}G4 Q�7 Z G"JB8s9\<,p\3놳QPܺC!Y—H_e>Ny$s>FQM` :l0K$brP x (�T#VH}*`Bk?粑> .A!!c%EUpSY(a"#<�G漳hV'ŗvWEvjiUNB£Zgא %"ʅCm/tL{ w g a}əSAܔ9;! G6nyqwu{nt{j-NX΁:յHE缢DMi&ݻ"g/~*w7ݵ;YcozULqw\u^ D8U"c*p@${%H`�Vgpt|*"FF%QVT !:j LIc^aČ!d =\q/i^kЍ+x9X bf%i: Z}|ޤepTc*Nd/;ݣzZ*F׬.6ZExGi{z~UPӯ?;JX?볺4yM2I]�.{yicݮk]h|5C=F14h}  CWăݗ,˲, lnO %@q77eLW7!#>^Ƨ7Ƨ3/_6n-zӮ'Zo|/z7>mi5\++q?Q%N.jzy/=h2g۲]$s.)ՄNMҭ }K&tv=њф&t4 jeDҽbVe{3d*:Ǒ"3j:g/=l7m܉uto%`1)Q˲=['4ݽmN"qk�F^v&ǶpMx[5ք&˺ք&5Jj73P=JSg#,٢k^LX)P,.)%ў)ڃ!MZIԬR-Y}Kfv=њUҬf4Yjٺ6573Q[]G*}LB{)t[PPK����R$Bp-s���� ���settings.xmlY[w:~_uV+bYHkm[Ts ' "'δL[:|ξKWqDɹR;T $.+ATrF uCqr7s%dIGIySM@|9"K[2"hVQFCʦZѨz7C]JTTҟDɄTG]|_ج_)ҿ$hnN;W$is`So։'Y#CA0q ;JK=1[$J!O̶a߅h:*V?m"\B/ZMVw k/#(LځJ:k6A͈:�R)ozNqz#!¸Hsy m ݥ =S"�;(!- >#FG֗+(@wZ 4GCڎa\3 \;W-޽+R/q,7h'wB?@8®;$xWF C[y$8bz=x& x`>T}P6S49K5iߎ2\ ĤQO尬:e_PȂcߡa6D' Pp]K:.„\nznX1g񷦏{CC sPvw6__"ޏ;cgS5IXU9#d%9mHCUg(/[D^0>%LE�!^,hJKN{ dȭG#tEp�GmD�=~v|+{8TWvz{H/iGLq<zo }}i\7u2nN7]ޕ~C[򹣂Q#YKa*qGK\Fwz s{ܺ)&7E[kp~3*P{=\m=r܋I`kÆ|^:D`MnW9d@ߘ\iy+6? #:GBl,,~y &1F?}XteB8Q2aǻ"L٭IPK����R$B\���� ���styles.xmlXێ6}W"Hʒ}H iNK̆"II}մEz%yvf4cN#puU:<!,ۺɻwv4%1$<.s̔'ՙb0{uK6I"7 XnTfЦSv([*nmiRaȢrܖN:-X-z)bH/[T:]0"ߜ6 (5$1ژU+?m|bjBc"5ŵa^'{es9áݥDԖv)imyyqw7]ЧYIE cDqL]j!uiR){ [];cך~+Ob(z޺QnNKeAT >"Atp',B})6ݤjĪE{fXXOOrTDOS6˞WHPZWLrʭn1U 2P Ty^x 1}[ZҺM|CJff#%S+B_+S!3\U*ǎ.uq^yS=4YIl۶\uNJ+SA9*'#L  -q)1kap!9r [_JpuWc[9иS1>磯O3K6)NV)TnJ�>\Uw^c,o- 9*g Q-3zT* Zul6)Vvq?#yai}Du ϕ L$p,`5FrJ ȉ$z\AQp$;SZ~_EP[=@>1o8Prf<C#$'cЗWJkiB`@aO݊E֍Aƭ˰X NMiEkU9Yī >AD~ֺaB'w }RtV;:_yrTxjbl]͔sHd/sclutq%G"3S}J:XM5?3~U}ga?Hɂ\g{. 7w9;WH`Wa-/eI@\xjAnv!7nIm:ɑl\]=gd`i\+v<`lệ7~)\KAg扄<&-G>耪}(??I?₋Y#cC>_yaq(چ!][=lY Oe05 vw^y=w#ѩ}�M5~RoYa7]v_D PK����R$BJÍ��8�������������������meta.xmlPK����R$Bl9,���.������������������mimetypePK����R$B�����������'���������������Configurations2/accelerator/current.xmlPK����R$B?%������������������L��META-INF/manifest.xmlPK����R$B_?���� �������������s��content.xmlPK����R$Bp-s���� ������������� ��settings.xmlPK����R$B\���� �������������A��styles.xmlPK��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/non-profit-audit-reports/unpaid-accruals-report.plx����������������������0000775�0000000�0000000�00000007704�12660234410�0027433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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: ������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0016205�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/GenerateLatexExpeneseReport.pl���������������������������������������0000775�0000000�0000000�00000025771�12660234410�0024202�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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"`; } �������ledger-3.1.1+dfsg1/contrib/raw/MetadataExample.dat��������������������������������������������������0000664�0000000�0000000�00000011543�12660234410�0021737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������; 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 �������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/README���������������������������������������������������������������0000664�0000000�0000000�00000000411�12660234410�0017061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������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. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/VerifyImages.sh������������������������������������������������������0000775�0000000�0000000�00000000273�12660234410�0021140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/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 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/dotemacs.el����������������������������������������������������������0000664�0000000�0000000�00000023071�12660234410�0020331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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))) )))))) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/ledger-matching.el���������������������������������������������������0000664�0000000�0000000�00000032012�12660234410�0021557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;; 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) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/raw/ledger-shell-environment-functions�����������������������������������0000664�0000000�0000000�00000005567�12660234410�0025064�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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) " } �����������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/repl.sh������������������������������������������������������������������0000775�0000000�0000000�00000000354�12660234410�0016717�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash EXEC=$(which ledger) if [[ -z "$EXEC" ]]; then EXEC=$HOME/Products/ledger/ledger fi if [[ ! -x "$EXEC" ]]; then echo Cannot find Ledger executable exit 1 fi LESS=--quit-if-one-screen exec $EXEC --pager less "$@" ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/report�������������������������������������������������������������������0000775�0000000�0000000�00000001032�12660234410�0016651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # This script facilities plotting of a ledger register report. If you # use OS/X, and have AquaTerm installed, you will probably want to set # LEDGER_TERM to "aqua". # # Examples of use: # # report -j -M reg food # plot monthly food costs # report -J reg checking # plot checking account balance if [ -z "$LEDGER_TERM" ]; then LEDGER_TERM="x11 persist" fi (cat <<EOF; ledger "$@") | gnuplot set terminal $LEDGER_TERM set xdata time set timefmt "%Y-%m-%d" plot "-" using 1:2 with lines EOF ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/tc�����������������������������������������������������������������������0000775�0000000�0000000�00000000104�12660234410�0015743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh timeclock out proj="$1" shift timeclock in "$proj" "$@" ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/ti�����������������������������������������������������������������������0000775�0000000�0000000�00000000065�12660234410�0015757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh proj="$1" shift timeclock in "$proj" "$@" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/to�����������������������������������������������������������������������0000775�0000000�0000000�00000000036�12660234410�0015763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh timeclock out "$@" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/contrib/trend��������������������������������������������������������������������0000775�0000000�0000000�00000001237�12660234410�0016461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # This script requires Python support. # # To use, just run "trend" with the accounts to compute the trend for: # # trend dining # # The trend values are not terribly meaningful, but this gives an # example of how Python can be used to create more complex reports. ledger --import-stdin -T "@rdev()" reg "$@" <<EOF import ledger mean = ledger.parse_value_expr ("AT") last_mean = None last_dev = None def rdev (details): global last_mean, last_dev mval = mean.compute (details) if last_mean is None: dev = ledger.Value () else: dev = mval - last_mean dev = (last_dev + dev) / 2 last_mean = mval last_dev = dev return dev EOF �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/default.nix����������������������������������������������������������������������0000664�0000000�0000000�00000002212�12660234410�0016115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ stdenv, fetchgit, cmake, boost, gmp, mpfr, libedit, python , texinfo, gnused }: let rev = "20141005"; in stdenv.mkDerivation { name = "ledger-3.1.0.${rev}"; src = builtins.filterSource (path: type: type != "unknown") ./.; buildInputs = [ cmake boost gmp mpfr libedit python texinfo gnused ]; enableParallelBuilding = true; # Skip byte-compiling of emacs-lisp files because this is currently # broken in ledger... postInstall = '' mkdir -p $out/share/emacs/site-lisp/ cp -v "$src/lisp/"*.el $out/share/emacs/site-lisp/ ''; meta = { homepage = "http://ledger-cli.org/"; description = "A double-entry accounting system with a command-line reporting interface"; license = "BSD"; longDescription = '' Ledger is a powerful, double-entry accounting system that is accessed from the UNIX command-line. This may put off some users, as there is no flashy UI, but for those who want unparalleled reporting access to their data, there really is no alternative. ''; platforms = stdenv.lib.platforms.all; maintainers = with stdenv.lib.maintainers; [ simons the-kenny jwiegley ]; }; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0014521�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/CMakeLists.txt���������������������������������������������������������������0000664�0000000�0000000�00000011036�12660234410�0017262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 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 ######################################################################## configure_file( ${PROJECT_SOURCE_DIR}/doc/version.texi.in ${PROJECT_BINARY_DIR}/doc/version.texi) 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() ######################################################################## # BUILD_WEB_DOCS implies BUILD_DOCS if (BUILD_WEB_DOCS) set(BUILD_DOCS 1) endif() if (BUILD_DOCS) find_program(MAKEINFO makeinfo) find_program(TEXI2PDF texi2pdf) find_program(TEX tex) find_program(MAN2HTML man2html) find_program(GROFF groff) set(ledger_info_files ledger3.texi ledger-mode.texi) if (NOT MAKEINFO) message(WARNING "Could not find makeinfo. Info version of documentation cannot be built.") endif() if (NOT TEXI2PDF OR NOT TEX) message(WARNING "Could not find texi2pdf or tex. PDF version of documentation will not be built.") endif() endif() ######################################################################## foreach(file ${ledger_info_files}) get_filename_component(file_base ${file} NAME_WE) if (MAKEINFO) add_custom_command(OUTPUT ${file_base}.info COMMAND makeinfo --force --no-split -o ${file_base}.info ${CMAKE_CURRENT_SOURCE_DIR}/${file} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file} VERBATIM) list(APPEND ledger_doc_files ${file_base}.info) endif() if (BUILD_WEB_DOCS AND MAKEINFO) 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() if (TEXI2PDF AND TEX) if (BUILD_A4_PDF) set(papersize --texinfo=@afourpaper) endif() add_custom_command(OUTPUT ${file_base}.pdf COMMAND texi2pdf ${papersize} -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 (MAN2HTML) 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) elseif(GROFF) add_custom_command(OUTPUT ledger.1.html COMMAND ${BASH} -c "groff -mandoc -Thtml ${CMAKE_CURRENT_SOURCE_DIR}/ledger.1 > ledger.1.html" DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ledger.1 VERBATIM) list(APPEND ledger_doc_files ledger.1.html) else() message(FATAL_ERROR "Could not find man2html or groff. HTML version of man page cannot be built.") endif() 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) foreach(file ${ledger_doc_files}) get_filename_component(file_ext ${file} EXT) if(file_ext STREQUAL ".info") if(CMAKE_INSTALL_INFODIR) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${file} DESTINATION ${CMAKE_INSTALL_INFODIR} COMPONENT doc) endif() elseif(CMAKE_INSTALL_DOCDIR) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${file} DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) endif() endforeach() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/Doxyfile.in������������������������������������������������������������������0000664�0000000�0000000�00000234657�12660234410�0016655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Doxyfile 1.8.3 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" "). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or 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 # if some version control system is used. PROJECT_NUMBER = 3.0 # 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 = @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 # 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, 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). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = 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 # 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 # 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. 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 = # 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 # (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 # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # 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 # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # 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 # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # 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 # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When 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 # 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 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 # 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 # will be included in the documentation. EXTRACT_PRIVATE = NO # 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 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. # 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 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. # 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 # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = 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 # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the 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 # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = YES # 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. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # 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 # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if section-label ... \endif # and \cond section-label ... \endcond blocks. ENABLED_SECTIONS = # 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 # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # 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 # 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 # 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 # is used as the file version. See the manual for examples. 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. 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 = # 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 # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. # jww (2009-01-31): Enable this toward the end 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 # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # 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 # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. # 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 # 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++ *.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. # If left blank NO is used. RECURSIVE = NO # 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 = # 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 # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command <filter> <input-file>, where <filter> # is the value of the INPUT_FILTER tag, and <input-file> is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # 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 # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C, 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 # 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 # called/used by that function will be listed. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the 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 # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = 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 # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. 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 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 therefore 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) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # 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 # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. 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 # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # 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. # The path specified is relative to the HTML output folder. QCH_FILE = # 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 # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # 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 QHP_CUST_FILTER_NAME = # 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>. QHP_CUST_FILTER_ATTRS = # 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>. 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 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 # 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 # 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 # generate Latex output. GENERATE_LATEX = YES # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # 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 # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, 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 # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # 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! 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 # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # 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 # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load 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 = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = 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 # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # 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 preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # 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 #--------------------------------------------------------------------------- # 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 = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option 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 # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # 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 default size is 10pt. DOT_FONTSIZE = 11 # 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 = # 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 # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # 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 # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # 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 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 # 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 = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The 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 # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 1000 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is 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) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES ���������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/GLOSSARY.md������������������������������������������������������������������0000664�0000000�0000000�00000022576�12660234410�0016322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ACCOUNTING GLOSSARY --- Accounting and bookkeeping represent an entire field of human effort and has evolved its own specialized vocabulary. Accounting hopes to summarize and add understanding to where the money is going. **Account**: A category for grouping together amounts from similar transactions. Each account has a name, which is usually capitalized, and an account type. Accounts are often organized into a hierarchy when it helps understanding. For example, a coffee shop might have Coffee, Merchandise, and Equipment as accounts but arranged under an Inventory account because different decisions are made on the total inventory rather than just coffee. A hierarchy can be part of the account name in Ledger, e.g., "Assets:Inventory:Coffee". Note that the Ledger software usually creates the list of accounts on the fly: accounts are created when transactions use them. **Account Type**: Each account has a type of Asset, Liability, Equity, Income, or Expense. Assets represent something owned, e.g., Cash or Inventory. Liabilities represent sometime owed, e.g., a Loan or Mortgage. Equity, also called capital, is everything owned minus everything owed (Assets - Liabilities). It is the financial measure of how much you are ahead. Income is money earned somewhere, which puts you more ahead. Expenses is money spent somewhere, which puts you less ahead. The type of account determines if a debit represents an increase or decrease in an account. For example, Inventory is an asset so a transaction debiting Inventory would increase its value. Assets and Expenses increase with debits and decrease with credits; Liabilities, Equity, and Expenses increase with credits and decrease with debits. **Journal**: A record of all the financial transactions of a person or firm. This data of where money goes can be collated into reports. This used to be done with a physical book, called a ledger, where each account was on one page. Each debit or credit in the journal was transferred to the appropriate account page and the pages were totaled to produce reports. This process is now done with the Ledger software which creates reports from the journal. A journal is sometimes called a register. **Posting**: A single debit or credit line of a transaction. A posting comprises an account and the debit or credit amount. It also inherits the shared description and date from the transaction. In the Ledger software, a posting may also have metadata and an account state. **Report**: A summary made from a journal of transactions. Each transaction affects accounts and those effects are collated and totaled. The two most common reports are the balance sheet, which shows what is owned and owed on a specific date, and the cash flow statement, which shows how money was earned and spent over a period. The cash flow statement is also called a profit and loss statement or an income statement. **Transaction**: Our financial lives are recorded as a series of transactions. Each transaction has a specific date, an equal total of debits and credits affecting accounts, and some sort of description. For example, "On January 1, pay $100 with check #243 from Checking to Utilities for my Verizon phone bill" is a transaction. A credit of $100 decreases my Checking asset, while a balancing debit of $100 increases my Utility expense. A transaction needs at least two *postings*, meaning account debits or credits, but can be as complicated as humans can make finances. LEDGER GLOSSARY --- The Ledger software also has its own terms. **Automated Transaction**: a command directive that modifies subsequent transactions that match an expression. An automated transaction can add additional postings to a transaction, add metadata, or change transaction amounts. Reports can be filter postings modified or generated by an automated transaction. [§ Automated Transactions](http://www.ledger-cli.org/3.0/doc/ledger3.html#Automated-Transactions); [§ Concrete Example of Automated Transactions](http://www.ledger-cli.org/3.0/doc/ledger3.html#Concrete-Example-of-Automated-Transactions) **Command Directive**: a command in a journal file to change how subsequent lines and transactions in a journal file are processed. Command directives control processing, set default values for subsequent accounts and transactions, or override parts of subsequent transactions. A directive line begins with name of the directive and may have additional arguments or additional indented lines. The single letters *AbCDhIiNOoY* are aliased to other command directives, providing compatibility with the ancient past. The characters **'='** and **'-'** are command directives for a automatic transactions and periodic transactions, respectively. [§ Command Directives](http://www.ledger-cli.org/3.0/doc/ledger3.html#Command-Directives) **Commodity**: any currency, stock, time or resource to be tracked numerically. While many people only track money in Ledger, Ledger can track different resources and manage rules to convert between them. The system is flexible enough for the needs of very different users. Some track billable time, converting minutes and hours into dollars. Others track multiple currencies. Still others track the purchase and sale of stocks. Each commodity is separate unless a conversion rule is given. [§ Commodities and Currencies](http://www.ledger-cli.org/3.0/doc/ledger3.html#Commodities-and-Currencies); [§ Currencies and Commodities](http://www.ledger-cli.org/3.0/doc/ledger3.html#Currency-and-Commodities); [§ Accounts and Inventories](http://www.ledger-cli.org/3.0/doc/ledger3.html#Accounts-and-Inventories); [§ Posting Cost](http://www.ledger-cli.org/3.0/doc/ledger3.html#Posting-cost) *(and next ten sections)*; [§ Commodity Reporting](http://www.ledger-cli.org/3.0/doc/ledger3.html#Commodity-Reporting) **Effective Date**: an optional, second date information item in for a posting or transaction. Some use the effective date for when work is billed or when a check has cleared. The `--effective-date` option causes the effective date to override the transaction's initial date for that report. [§ Effective Dates](http://www.ledger-cli.org/3.0/doc/ledger3.html#Effective-Dates); **Journal File**: the text input file for ledger, sometimes called a register file. A journal file is a series of transactions, command directives, and comments. Command directives start with the single word name of the directive at the beginning of the line and include any following indented lines. Transactions start with a date a the beginning of the line and include any indented lines following. The journal file is expected to be encoded as ASCII or UTF-8 text. **Periodic Transaction**: the estimate of a transaction that would occur periodically, e.g., a monthly expense. These estimates are only used in budgeting and forecasting reports using the `--budget`, `--forecast`, or `--unbudgeted` options. [§ Budgeting and Forecasting](http://www.ledger-cli.org/3.0/doc/ledger3.html#Budgeting-and-Forecasting) **Transaction Code**: an optional item in a transaction or posting often used to record a check number or bank code. Certain custom reports can report this code. [§ Codes](http://www.ledger-cli.org/3.0/doc/ledger3.html#Codes); [§ Format Expressions](http://www.ledger-cli.org/3.0/doc/ledger3.html#Format-Expressions) **Transaction Metadata**: a term for comments and tags annotating a transaction. Comments indented with a transaction will be stored with each posting of a transaction. Tags are words in comments followed by colons. Tags can be used as filters in reports and certain tags, "Payee" or "Value", may affect fields of the transaction. [§ Metadata](http://www.ledger-cli.org/3.0/doc/ledger3.html#Metadata), [§ Applying Metadata to every matched posting](http://www.ledger-cli.org/3.0/doc/ledger3.html#Applying-metadata-to-every-matched-posting), [§ Applying Metadata to the generated posting](http://www.ledger-cli.org/3.0/doc/ledger3.html#Applying-metadata-to-the-generated-posting) **Transaction State**: a state of *cleared*, *pending*, or *uncleared* on each posting. The state is usually set for an entire transaction at once with a mark after the date. The marks are ***** (cleared), **!** (pending), or no mark (uncleared). The interpretation of this state is up to the user, but is typically used in bank reconciliations or differentiating time worked versus billed. Ledger supports reports and filters based on state. [§ Transaction State](http://www.ledger-cli.org/3.0/doc/ledger3.html#Transaction-state); [§ Cleared Report]( http://www.ledger-cli.org/3.0/doc/ledger3.html#Cleared-Report) **Virtual Posting**: an annotation posting in a transaction, similar in form as a regular posting but not required to balance debits and credits. It is often used to support [Fund Accounting](http://en.wikipedia.org/wiki/Fund_accounting) and various reports will collate and summarize virtual postings. Virtual postings should not be confused with virtual posting costs. [§ Virtual Postings](http://www.ledger-cli.org/3.0/doc/ledger3.html#Virtual-postings) [§ Working with Multiple Funds and Accounts](http://www.ledger-cli.org/3.0/doc/ledger3.html#Working-with-multiple-funds-and-accounts) ����������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/NEWS�������������������������������������������������������������������������0000664�0000000�0000000�00000103573�12660234410�0015231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Ledger NEWS * 3.1.1 - Added a --no-revalued option - Improved Embedded Python Support - Use ./.ledgerrc if ~/.ledgerrc doesn't exist - Fixed parsing of transactions with single-character payees and comments - Fixed crash when using -M with empty result - Fixed sorting for option --auto-match - Fixed treatment of "year 2015" and "Y2014" directives - Fixed crash when using --trace 10 or above - Build fix for boost 1.58, 1.59, 1.60 - Build fix for Cygwin - Fixed Util and Math tests on Mac OS X - Various documentation improvements - Examples in the documentation are tested just like unit tests - Add continuous integration (https://travis-ci.org/ledger/ledger) * 3.1 - Changed the definition of cost basis to preserve the original cost basis when a gain or loss is made (if you bought 1 AAA for $10 and then sold it for $12, ledger would previously take $12 as the cost; the original cost of $10 is preserved as the cost basis now, which addresses strange behavior with -B after a capital gain or loss is made). - Incorrect automatic Equity:Capital Gains and Equity:Capital Loss entries are no longer generated when a commodity is sold for loss or profit. - Support for virtual posting costs. - The option --permissive now quiets balance assertions - Removed SHA1 files due to license issues and use boost instead. - Added option --no-pager to disable the pager. - Added option --no-aliases to completely disable alias expansion - Added option --recursive-aliases to expand aliases recursively - Support payee "uuid" directive. - Bug fix: when a status flag (! or *) is explicitly specified for an individual posting, it always has a priority over entire transaction status. - Bug fix: don't lose commodity when cost is not separated by whitespace - Improved backwards compatibility with ledger 2.x - Build fix for GCC 4.9 - Build fix for boost 1.56 - Many improvements to ledger-mode, including fontification - More test cases and unit tests - Contrib: Added script to generate commodities from ISO 4217 * 3.0 Due to the magnitude of changes in 3.0, only changes that affect compatibility with 2.x files and usage is mentioned here. For a description of new features, please see the manual. - The option -g (--performance) was removed. - The balance report now defaults to showing all relevant accounts. This is the opposite of 2.x. That is, "bal" in 3.0 does what "-s bal" did in 2.x. To see 2.6 behavior, use "bal -n" in 3.0. The -s option no longer has any effect on balance reports. * 2.6.3 - Minor fixes to allow for compilation with gcc 4.4. * 2.6.2 - Bug fix: Command-line options, such as -O, now override init-file options such as -V. - Bug fix: "cat data | ledger -f -" now works. - Bug fix: --no-cache is now honored. Previously, it was writing out a cache file named "<none>". - Bug fix: Using %.2X in a format string now outputs 2 spaces if the state is cleared. * 2.6.1 - Added the concept of "balance setting transactions": # Setting an account's balance You can now manually set an account's balance to whatever you want, at any time. Here's how it might look at the beginning of your Ledger file: 2008/07/27 Starting fresh Assets:Checking = $1,000.00 Equity:Opening Balances If Assets:Checking is empty, this is no different from omitting the "=". However, if Assets:Checking did have a prior balance, the amount of the transaction will be auto-calculated so that the final balance of Assets:Checking is now $1,000.00. Let me give an example of this. Say you have this: 2008/07/27 Starting fresh Assets:Checking $750.00 Equity:Opening Balances 2008/07/27 Starting fresh Assets:Checking = $1,000.00 Equity:Adjustments These two entries are exactly equivalent to these two: 2008/07/27 Starting fresh Assets:Checking $750.00 Equity:Opening Balances 2008/07/27 Starting fresh Assets:Checking $250.00 Equity:Adjustments The use of the "=" sign here is that it sets the transaction's amount to whatever is required to satisfy the assignment. This is the behavior if the transaction's amount is left empty. # Multiple commodities As far as commodities go, the = sign only works if the account balance's commodity matches the commodity of the amount after the equals sign. However, if the account has multiple commodities, only the matching commodity is affected. Here's what I mean: 2008/07/24 Opening Balance Assets:Checking = $250.00 ; we force set it Equity:Opening Balances 2008/07/24 Opening Balance Assets:Checking = EC 250.00 ; we force set it again Equity:Opening Balances This is an error, because $250.00 cannot be auto-balanced to match EC 250.00. However: 2008/07/24 Opening Balance Assets:Checking = $250.00 ; we force set it again Assets:Checking EC 100.00 ; and add some EC's Equity:Opening Balances 2008/07/24 Opening Balance Assets:Checking = EC 250.00 ; we force set the EC's Equity:Opening Balances This is *not* an error, because the latter auto-balancing transaction only affects the EC 100.00 part of the account's balance; the $250.00 part is left alone. # Checking statement balances When you reconcile a statement, there are typically one or more transactions which result in a known balance. Here's how you specify that in your Ledger data: 2008/07/24 Opening Balance Assets:Checking = $100.00 Equity:Opening Balances 2008/07/30 We spend money, with a known balance afterward Expenses:Food $20.00 Assets:Checking = $80.00 2008/07/30 Again we spend money, but this time with all the info Expenses:Food $20.00 Assets:Checking $-20.00 = $60.00 2008/07/30 This entry yield an 'unbalanced' error Expenses:Food $20.00 Assets:Checking $-20.00 = $30.00 The last entry in this set fails to balance with an unbalanced remainder of $-10.00. Either the entry must be corrected, or you can have Ledger deal with the remainder automatically: 2008/07/30 The fixed entry Expenses:Food $20.00 Assets:Checking $-20.00 = $30.00 Equity:Adjustments # Conclusion This simple feature has all the utility of @check, plus auto-balancing to match known target balances, plus the ability to guarantee that an account which uses only one commodity does contain only that commodity. This feature slows down textual parsing slightly, does not affect speed when loading from the binary cache. - The rest of the changes in the version is all bug fixes (around 45 of them). * 2.6.0.90 - Gnucash parser is fixed. - Fix a memory leak bug in the amount parser. - (This feature is from 2.6, but was not documented anywhere): Commodities may now specify lot details, to assign in managing set groups of items, like buying and selling shares of stock. For example, let's say you buy 50 shares of AAPL at $10 a share: 2007/01/14 Stock purchase Assets:Brokerage 50 AAPL @ $10 Assets:Brokerage Three months later, you sell this "lot". Based on the original purchase information, Ledger remembers how much each share was purchased for, and the date on which it was purchased. This means you can sell this specific lot by price, by date, or by both. Let's sell it by price, this time for $20 a share. 2007/04/14 Stock purchase Assets:Brokerage $1000.00 Assets:Brokerage -50 AAPL {$10} @ $20 Income:Capital Gains $-500.00 Note that the Income:Capital Gains line is now required to balance the transaction. Because you sold 50 AAPL at $20/share, and because you are selling shares that were originally valued at $10/share, Ledger needs to know how you will "balance" this difference. An equivalent Expenses:Capital Loss would be needed if the selling price were less than the buying price. Here's the same example, this time selling by date and price: 2007/04/14 Stock purchase Assets:Brokerage $1000.00 Assets:Brokerage -50 AAPL {$10} [2007/01/14] @ $20 Income:Capital Gains $-500.00 If you attempt to sell shares for a date you did not buy them, note that Ledger will not complain (as it never complains about the movement of commodities between accounts). In this case, it will simply create a negative balance for such shares within your Brokerage account; it's up to you to determine whether you have them or not. - To facilitate lot pricing reports, there are some new reporting options: --lot-prices Report commodities with different lot prices as if they were different commodities. Otherwise, Ledger just gloms all the AAPL shares together. --lot-dates Separate commodities by lot date. Every transaction that uses the '@' cost specifier will have an implicit lot date and lot price. --lot-tags Separate commodities by their arbitrary note tag. Note tags may be specified using (note) after the commodity. --lots Separate commodities using all lot information. * 2.6 - The style for eliding long account names (for example, in the register report) has been changed. Previously Ledger would elide the end of long names, replacing the excess length with "..". However, in some cases this caused the base account name to be missing from the report! What Ledger now does is that if an account name is too long, it will start abbreviating the first parts of the account name down to two letters in length. If this results in a string that is still too long, the front will be elided -- not the end. For example: Expenses:Cash ; OK, not too long Ex:Wednesday:Cash ; "Expenses" was abbreviated to fit Ex:We:Afternoon:Cash ; "Expenses" and "Wednesday" abbreviated ; Expenses:Wednesday:Afternoon:Lunch:Snack:Candy:Chocolate:Cash ..:Af:Lu:Sn:Ca:Ch:Cash ; Abbreviated and elided! As you can see, it now takes a very deep account name before any elision will occur, whereas in 2.x elisions were fairly common. - In addition to the new elision change mentioned above, the style is also configurable: --truncate leading ; elide at the beginning --truncate middle ; elide in the middle --truncate trailing ; elide at end (Ledger 2.x's behavior) --truncate abbrev ; the new behavior --abbrev-len 2 ; set length of abbreviations These elision styles affect all format strings which have a maximum width, so they will also affect the payee in a register report, for example. In the case of non-account names, "abbrev" is equivalent to "trailing", even though it elides at the beginning for long account names. - Error reporting has been greatly improving, now showing full contextual information for most error messages. - Added --base reporting option, for reporting convertible commodities in their most basic form. For example, if you read a timeclock file with Ledger, the time values are reported as hour and minutes -- whichever is the most compact form. But with --base, Ledger reports only in seconds. NOTE: Setting up convertible commodities is easy; here's how to use Ledger for tracking quantities of data, where the most compact form is reported (unless --base is specified): C 1.00 Kb = 1024 b C 1.00 Mb = 1024 Kb C 1.00 Gb = 1024 Mb C 1.00 Tb = 1024 Gb - Added --ansi reporting option, which shows negative values in the running total column of the register report as red, using ANSI terminal codes; --ansi-invert makes non-negative values red (which makes more sense for the income and budget reports). The --ansi functionality is triggered by the format modifier "!", for example the register reports uses the following for the total (last) column: %!12.80T At the moment neither the balance report nor any of the other reports make use of the ! modifier, and so will not change color even if --ansi is used. However, you can modify these report format strings yourself in ~/.ledgerrc if you wish to see red coloring of negative sums in other places. - Added --only predicate, which occurs during transaction processing between --limit and --display. Here is a summary of how the three supported predicates are used: --limit "a>100" This flag limits computation to *only transactions whose amount is greater than 100 of a given commodity*. It means that if you scan your dining expenses, for example, only individual bills greater than $100 would be calculated by the report. --only "a>100" This flag happens much later than --limit, and corresponding more directly to what one normally expects. If --limit isn't used, then ALL your dining expenses contribute to the report, *but only those calculated transactions whose value is greater than $100 are used*. This becomes important when doing a monthly costs report, for example, because it makes the following command possible: ledger -M --only "a>100" reg ^Expenses:Food This shows only *months* whose amount is greater than 100. If --limit had been used, it would have been a monthly summary of all individual dinner bills greater than 100 -- which is a very different thing. --display "a>100" This predicate does not constrain calculation, but only display. Consider the same command as above: ledger -M --display "a>100" reg ^Expenses:Food This displays only lines whose amount is greater than 100, *yet the running total still includes amounts from all transactions*. This command has more particular application, such as showing the current month's checking register while still giving a correct ending balance: ledger --display "d>[this month]" reg Checking Note that these predicates can be combined. Here is a report that considers only food bills whose individual cost is greater than $20, but shows the monthly total only if it is greater than $500. Finally, we only display the months of the last year, but we retain an accurate running total with respect to the entire ledger file: ledger -M --limit "a>20" --only "a>200" \ --display "year == yearof([last year])" reg ^Expenses:Food - Added new "--descend AMOUNT" and "--descend-if VALEXPR" reporting options. For any reports that display valued transactions (i.e., register, print, etc), you can now descend into the component transactions that made up any of the values you see. For example, say you request a --monthly expenses report: $ ledger --monthly register ^Expenses Now, in one of the reported months you see $500.00 spent on Expenses:Food. You can ask Ledger to "descend" into, and show the component transactions of, that $500.00 by respecifying the query with the --descend option: $ ledger --monthly --descend "\$500.00" register ^Expenses The --descend-if option has the same effect, but takes a value expression which is evaluated as a boolean to locate the desired reported transaction. - Added a "dump" command for creating binary files, which load much faster than their textual originals. For example: ledger -f huge.dat -o huge.cache dump ledger -f huge.cache bal The second command will load significantly faster (usually about six times on my machine). - There have a few changes to value expression syntax. The most significant incompatibilities being: * Equality is now ==, not = * The U, A, and S functions now requires parens around the argument. Whereas before At was acceptable, now it must be specified as A(t). * The P function now always requires two arguments. The old one-argument version P(x) is now the same as P(x,m). The following value expression features are new: * A C-like comma operator is supported, where all but the last term are ignored. The is significant for the next feature: * Function definitions are now supported. Scoping is governed by parentheses. For example: (x=100, x+10) ; yields 110 as the result (f(x)=x*2,f(100)) ; yields 200 as the result * Identifier names may be any length. Along with this support comes alternate, longer names for all of the current one-letter value expression variables: Old New --- --- m now a amount a amount b cost i price d date X cleared Y pending R real L actual n index N count l depth O total B cost_total I price_total v market V market_total g gain G gain_total U(x) abs(x) S(x) quant(x), quantity(x) comm(x), commodity(x) setcomm(x,y), set_commodity(x,y) A(x) mean(x), avg(x), average(x) P(x,y) val(x,y), value(x,y) min(x,y) max(x,y) - There are new "parse" and "expr" commands, whose argument is a single value expression. Ledger will simply print out the result of evaluating it. "parse" happens before parsing your ledger file, while "expr" happens afterward. Although "expr" is slower as a result, any commodities you use will be formatted based on patterns of usage seen in your ledger file. These commands can be used to test value expressions, or for doing calculation of commoditized amounts from a script. A new "--debug" will also dump the resulting parse tree, useful for submitting bug reports. - Added new min(x,y) and max(x,y) value expression functions. - Value expression function may now be defined within your ledger file (or initialization file) using the following syntax: @def foo(x)=x*1000 This line makes the function "foo" available to all subsequent value expressions, to all command-line options taking a value expression, and to the new "expr" command (see above). * 2.5 - Added a new value expression regexp command: C// compare against a transaction amount's commodity symbol - Added a new "csv" command, for outputting results in CSV format. - Ledger now expands ~ in file pathnames specified in environment variables, initialization files and journal files. - Effective dates may now be specified for entries: 2004/10/03=2004/09/30 Credit card company Liabilities:MasterCard $100.00 Assets:Checking This entry says that although the actual transactions occurred on October 3rd, their effective date was September 30th. This is especially useful for budgeting, in case you want the transactions to show up in September instead of October. To report using effective dates, use the --effective option. - Actual and effective dates may now be specified for individual transactions: 2004/10/03=2004/09/30 Credit card company Liabilities:MasterCard $100.00 Assets:Checking ; [2004/10/10=2004/09/15] This states that although the actual date of the entry is 2004/10/03, and the effective date of the entry is 2004/09/30, the actual date of the Checking transaction itself is 2004/10/10, and its effective date is 2004/09/15. The effective date is optional (just specifying the actual date would have read "[2004/10/10]"). If no effective date is given for a transaction, the effective date of the entry is assumed. If no actual date is given, the actual date of the entry is assumed. The syntax of the latter is simply [=2004/09/15]. - To support the above, there is a new formatting option: "%d". This outputs only the date (like "%D") if there is no effective date, but outputs "ADATE=EDATE" if there is one. The "print" report now uses this. - To support the above, the register report may now split up entries whose component transactions have different dates. For example, given the following entry: 2005/10/15=2005/09/01 iTunes Expenses:Music $1.08 ; [2005/10/20=2005/08/01] Liabilities:MasterCard The command "ledger register" on this data file reports: 2005/10/20 iTunes Expenses:Music $1.08 $1.08 2005/10/15 iTunes Liabilities:MasterCard $-1.08 0 While the command "ledger --effective register" reports: 2005/08/01 iTunes Expenses:Music $1.08 $1.08 2005/09/01 iTunes Liabilities:MasterCard $-1.08 0 Although it appears as though two entries are being reported, both transactions belong to the same entry. - Individual transactions may now be cleared separately. The old syntax, which is still supported, clears all transactions in an entry: 2004/05/27 * Book Store Expenses:Dining $20.00 Liabilities:MasterCard The new syntax allows clearing of just the MasterCard transaction: 2004/05/27 Book Store Expenses:Dining $20.00 * Liabilities:MasterCard NOTE: This changes the output format of both the "emacs" and "xml" reports. ledger.el uses the new syntax unless the Lisp variable `ledger-clear-whole-entries' is set to t. - Removed Python integration support. - Did much internal restructuring to allow the use of libledger.so in non-command-line environments (such as GUI tools). * 2.4.1 - Corrected an issue that had inadvertently disabled Gnucash support. * 2.4 - Both "-$100.00" and "$-100.00" are now equivalent amounts. - Simple, inline math (using the operators +-/*, and/or parentheses) is supported in transactions. For example: 2004/05/27 Book Store Expenses:Dining $20.00 + $2.50 Liabilities:MasterCard This won't register the tax/tip in its own account, but might make later reading of the ledger file easier. - Use of a "catch all" account is now possible, which auto-balances entries that contain _only one transaction_. For sanity's sake this is not used to balance all entries, as that would make locating unbalanced entries a nightmare. Example: A Liabilities:MasterCard 2004/05/27 Book Store Expenses:Dining $20.00 + $2.50 This is equivalent to the entry in the previous bullet. - Entries that contain a single transaction with no amount now always balance, even if multiple commodities are involved. This means that the following is now supported, which wasn't previously: 2004/06/21 Adjustment Retirement 100 FUNDA Retirement 200 FUNDB Retirement 300 FUNDC Equity:Adjustments - Fixed several bugs relating to QIF parsing, budgeting and forecasting. - The configure process now looks for libexpat in addition to searching for libxmlparse+libxmltok (how expat used to be packaged). * 2.3 - The directive "!alias ALIAS = ACCOUNT" makes it possible to use "ALIAS" as an alternative name for ACCOUNT in a textual ledger file. You might use this to associate the single word "Bank" with the checking account you use most, for example. - The --version page shows the optional modules ledger was built with. - Fixed several minor problems, plus a few major ones dealing with imprecise date parsing. * 2.2 - Ledger now compiles under gcc 2.95. - Fixed several core engine bugs, and problems with Ledger's XML data format. - Erros in XML or Gnucash data now report the correct line number for the error, instead of always showing line 1. - 'configure' has been changed to always use a combination of both compile and link tests for every feature, in order to identify environment problems right away. - The "D <COMM>" command, released in 2.1, now requires a commoditized amount, such as "D $1,000.00". This sets not only the default commodity, but several flags to be used with all such commodities (such as whether numbering should be American or European by default). This entry may be used be many times; the most recent seen specifies the default for entries that follow. - The binary cache now remembers the price history database that was used, so that if LEDGER_PRICE_DB is silently changed, the cache will be thrown away and rebuilt. - OFX data importing is now supported, using libofx (http://libofx.sourceforge.net). configure will check if the library is available. You may need to add CPPFLAGS or LDFLAGS to the command-line for the appropriate headers and library to be found. This support is preliminary, and as such is not documented yet. - All journal entries now remember where they were read from. New format codes to access this information are: %S for source path, %B for beginning character position, and %E for ending character position. - Added "pricesdb" command, which is identical to "prices" except that it uses the same format as Ledger's usual price history database. - Added "output FILE" command, which attempts to reproduce the input journal FILE exactly. Meant for future GUI usage. This command relies on --write-hdr-format and --write-xact-format, instead of --print-format. - Added "--reconcile BALANCE" option, which attempts to reconcile all matching transactions to the given BALANCE, outputting those that would need to be "cleared" to match it. Using by the auto-reconciling feature of ledger.el (see below). "--reconcile-date DATE" ignores any uncleared transactions after DATE in the reconciling algorithm. Since the algorithm is O(n^2) (where 'n' is the number of uncleared transactions to consider), this could have a substantial impact. - In ledger.el's *Reconcile* mode ('C-c C-r' from a ledger-mode file): . 'a' adds a missing transaction . 'd' deletes the current transaction . 'r' attempts to auto-reconcile (same as 'C-u C-c C-r') . 's' or 'C-x C-s' will save the ledger data file and show the currently cleared balance . 'C-c C-c' commits the pending transactions, marking them cleared. This feature now works with Emacs 21.3. Also, the reconciler no longer needs to ask "how far back" to go. - To support the reconciler, textual entries may now have a "!" flag (pending) after the date, instead of a "*" flag (cleared). - There are a new set of value expression regexp commands: c// entry code p// payee w// short account name W// full account name e// transaction note This makes it possible to display transactions whose comment field matches a particular text string. For example: ledger -l e/{tax}/ reg prints out all the transactions with the comment "{tax}", which might be used to identify items related to a tax report. * 2.1 - Improved the autoconf system to be smarter about finding XML libs - Added --no-cache option, to always ignore any binary cache file - `ledger-reconcile' (in ledger.el) no longer asks for a number of days - Fixed %.XY format, where X is shorter than the string generated by Y - New directive for text files: "D <COMM>" specifies the default commodity used by the entry command * 2.0 This version represents a full rewrite, while preserving much of the original data format and command-line syntax. There are too many new features to describe in full, but a quick list: value expressions, complex date masks, binary caching of ledger data, several new reporting options, a simple way to specify payee regexps, calculation and display predicates, and two-way Python integration. Ledger also uses autoconf now, and builds as a library in addition to a command-line driver. ** Differences from 1.7 - changes in option syntax: -d now specifies the display predicate. To give a date mask similar to 1.7, use the -p (period) option. -P now generates the "by payee" report. To specify a price database to use, use --price-db. -G now generates a net gain report. To print totals in a format consumable by gnuplot, use -J. -l now specifies the calculation predicate. To emulate the old usage of "-l \$100", use: -d "AT>100". -N is gone. Instead of "-N REGEX", use: -d "/REGEX/?T>0:T". -F now specifies the report format string. The old meaning of -F now has little use. -S now takes a value expression as the sorting criterion. To get the old meaning of "-S", use "-S d". -n now means "collapse entries in the register report". The get the old meaning of -n in the balance report, use "-T a". -p now specifies the reporting period. You can convert commodities in a report using value expressions. For example, to display hours at $10 per hour: -T "O>={0.01h}?{\$10.00}*O:O" Or, to reduce totals, so that every $417 becomes 1.0 AU: -T "O>={\$0.01}?{1.0 AU}*(O/{\$417}):O" - The use of "+" and "-" in ledger files to specify permanent regexps has been removed. - The "-from" argument is no longer used by the "entry" command. Simply remove it. ** Features new to 2.0 - The most significant feature to be added is "value expressions". They are used in many places to indicate what to display, sorting order, how to calculate totals, etc. Logic and math operators are supported, as well as simple functions. See the manual. - If the environment variable LEDGER_FILE (or LEDGER) is used, a binary cache of that ledger is kept in ~/.ledger-cache (or the file given by LEDGER_CACHE). This greatly speeds up subsequent queries. Happens only if "-f" or "--file" is not used. - New 'xml' report outputs an XML version of what "register" would have displayed. This can be used to manipulate reported data in a more scriptable way. Ledger can also read as input the output from the "xml" report. If the "xml" report did not contain balanced entries, they will be balanced by the "<Unknown>" account. For example: ledger reg rent displays the same results as: ledger xml rent | ledger -f - reg rent - Regexps given directly after the command name now apply only to account names. To match on a payee, use "--" to separate the two kinds of regexps. For example, to find a payee named "John" within all Expenses accounts, use: ledger register expenses -- john Note: This command is identical (and internally converted) to: ledger -l "/expenses/|//john/" register - To include entries from another file into a specific account, use: !account ACCOUNT !include FILE !end - Register reports now show only matching account transactions. Use "-r" to see "related accounts" -- the account the transfer came from or went to (This was the old behavior in 1.x, but led to confusion). "-r" also works with balance reports, where it will total all the transactions related to your query. - Automated transactions now use value expressions for the predicate. The new syntax is: = VALUE-EXPR TRANSACTIONS... Only one VALUE-EXPR is supported (compared to multiple account regexps before). However, since value expression allow for logic chaining, there is no loss of functionality. Matching can also be much more comprehensive. - If Boost.Python is installed (libboost_python.a), ledger can support two-way Python integration. This feature is enabled by passing --enable-python to the "configure" script before building. Ledger can then be used as a module (ledger.so), as well as supporting Python function calls directly from value expressions. See main.py for an example of driving Ledger from Python. It implements nearly all the functionality of the C++ driver, main.cc. (This feature has yet to mature, and so is being offered as a beta feature in this release. It is mostly functional, and those curious are welcome to play with it.) - New reporting options: "-o FILE" outputs data to FILE. If "-", output goes to stdout (the default). -O shows base commodity values (this is the old behavior) -B shows basis cost of commodities -V shows market value of commodities -g reports gain/loss performance of each register item -G reports net gain/loss over time -A reports average transaction value (arithmetic mean) -D reports each transaction's deviation from the average -w uses 132 columns for the register report, rather than 80. Set the environment variable LEDGER_WIDE for this to be the default. "-p INTERVAL" allows for more flexible period reporting, such as: monthly every week every 3 quarters weekly from 12/20 monthly in 2003 weekly from last month until dec "-y DATEFMT" changes the date format used in all reports. The default is "%Y/%m/%d". -Y and -W print yearly and weekly subtotals, just as -M prints monthly subtotals. --dow shows cumulative totals for each day of the week. -P reports transactions grouped by payee -x reports the payee as the commodity; useful in some cases -j and -J replace the previous -G (gnuplot) option. -j reports the amounts column in a way gnuplot can consume, and -J the totals column. An example is in "scripts/report". "--period-sort EXPR" sorts transactions within a reporting period. The regular -S option sorts all reported transactions. * 1.7 - Pricing histories are now supported, so that ledger remembers the historical prices of all commodities, and can present register reports based on past and present market values as well as original cost basis. See the manual for more details on the new option switches. * 1.6 - Ledger can now parse timeclock files. These are simple timelogs that track in/out events, which can be maintained using my timeclock tool. By allowing ledger to parse these, it means that reporting can be done on them in the same way as ledger files (the commodity used is "h", for hours); it means that doing things like tracking billable hours for clients, and invoicing those clients to transfer hours into dollar values via a receivable account, is now trivial. See the docs for more on how to do this. - Began keeping a NEWS file. :) �������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/grammar.y��������������������������������������������������������������������0000664�0000000�0000000�00000012771�12660234410�0016351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file grammar.y * @version 3.0 * @author John Wiegley * * @brief Canonical BNF grammar for Ledger data files * * Extensions are permitted if: they are not required, and they are * backwards-compatible with this grammar. */ /* * There are three special terminals in this grammar, which violate its * context free nature: * * TEXT -- consumes all characters until the next terminal * or EOL (end of line) * WHITESPACE -- any amount of whitespace, not including EOL * STRING -- characters up to the next WHITESPACE or EOL * * BIGINT -- a number of any width, matching [0-9]+ * INT4 -- a four digit wide number * INT2 -- a two digit wide number * INT1 -- a one digit wide number * * Except for 1) the 'spacer' production (see below), 2) EOL, and 3) the * WHITESPACE required to begin a posting, whitespace is otherwise * ignored. * * Yes, this grammar is confusing and not so happy for machine readers, * but it was designed for the human author and reader. Once parsed, * the contents must be unambiguous, which means they can be output to * more rigorous formats for other programs to consume. */ /* * Journals * * A journal is a file which primarily contains xacts, among other elements. */ journal: journal_item journal | /* epsilon */ ; journal_item: whitespace directive | xact | ; whitespace: EOL | WHITESPACE EOL | ';' TEXT EOL | /* these next four are all ignored */ '*' TEXT EOL | ; directive: '@' word_directive EOL | '!' word_directive EOL | word_directive EOL | char_directive EOL ; word_directive: "include" TEXT | "account" TEXT | "end" | "alias" STRING '=' TEXT | "def" TEXT | TEXT WHITESPACE TEXT /* looked up in session (aka maybe Python) */ ; char_directive: 'i' date time TEXT | /* a timeclock.el "check in" */ 'I' date time TEXT | 'o' date time TEXT | /* a timeclock.el "check out" */ 'O' date time TEXT | 'h' TEXT EOL | 'b' TEXT EOL | 'D' amount | /* sets display parameters for a commodity */ 'A' TEXT | /* sets the "default balancing account" */ 'C' commodity '=' amount | /* specifies a commodity conversion */ 'P' date time commodity amount | /* a pricing history xact */ 'N' commodity | /* commodity's price is never downloaded */ 'Y' INT4 | /* sets the default year for date parsing */ '-' '-' STRING TEXT | /* specify command-line options in the file */ ; date: INT4 date_sep INT2 date_sep INT2 ; date_opt: '=' date | /* epsilon */ ; date_sep: '/' | '-' | '.' ; time: INT2 ':' INT2 ':' INT2 ; commodity: '"' TEXT '"' | STRING ; /* * Xacts * * Xacts are the atomic units of accounting, which are composed of * multiple postings between accounts, so long as it all balances in * the end. */ xact: plain_xact | periodic_xact | automated_xact ; plain_xact: date date_opt status_opt code_opt FULLSTRING note_opt EOL postings ; status_opt: status | /* epsilon */ ; status: '*' | '!' | /* epsilon */ ; code_opt: code | /* epsilon */ ; code: '(' TEXT ')' ; spacer: ' ' ' ' | '\t' | ' ' '\t' ; note_opt: spacer note | /* epsilon */ ; note: ';' TEXT ; /* ---------------------------------------------------------------------- */ periodic_xact: '~' period_expr note_opt EOL posting postings ; /* * A period expression has its own sub-grammar, which I don't quite have * the time to exhaustively describe now. See datetime.cc. It allows * for lots and lots of things, and is probably horribly ambiguous. */ period_expr: FULLSTRING ; /* ---------------------------------------------------------------------- */ automated_xact: '=' value_expr note_opt EOL posting postings ; /* * Value expressions are a algebraic math expressions very similar to * XPath (minus the path traversal items). This grammar needs fleshing * out also, since it's allowed in many places. */ value_expr: FULLSTRING ; /* * There is a serious ambiguity here which the parser resolves as * follows: if an amount_expr can be parsed as an amount, it's an * amount; otherwise, it's a value expression. */ quantity: neg_opt BIGINT decimal_opt ; neg_opt: '-' | /* epsilon */ ; decimal_opt: '.' BIGINT | /* epsilon */ ; annotation: lot_price_opt lot_date_opt lot_note_opt ; lot_date_opt: date | /* epsilon */ ; lot_date: '[' date ']' ; lot_price_opt: price | /* epsilon */ ; lot_price: '{' amount '}' ; lot_note_opt: note | /* epsilon */ ; lot_note: '(' string ')' ; amount: neg_opt commodity quantity annotation | quantity commodity annotation ; amount_expr: amount | value_expr ; /* * Postings * * Postings are the fundamental unit of accounting, and represent * the movement of commodities to or from an account. Thus, paying off * your credit card consists of two balancing postings: one that * withdraws money from your checking account, and another which pays * money to your credit institution. */ postings: posting postings | /* epsilon */ ; posting: WHITESPACE status_opt account values_opt note_opt EOL; account_name: FULLSTRING ; values_opt: spacer amount_expr price_opt | /* epsilon */ ; price_opt: price | /* epsilon */ ; price: '@' amount_expr | '@@' amount_expr /* in this case, it's the whole price */ ; account: account_name | '(' account_name ')' | '[' account_name ']' ; /* grammar.y ends here */ �������ledger-3.1.1+dfsg1/doc/ledger-mode.texi�������������������������������������������������������������0000664�0000000�0000000�00000127346�12660234410�0017615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������\input texinfo @c -*-texinfo-*- @setfilename ledger-mode.info @settitle Ledger: Command-Line Accounting @c Before release, run C-u C-c C-u C-a (texinfo-all-menus-update with @c a prefix arg). This updates the node pointers, which texinfmt.el @c needs. @copying Copyright @copyright{} 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: @itemize @item Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @item 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. @item 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. @end itemize 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 @dircategory Major Modes @direntry * Ledger Mode: (ledger-mode). Command-Line Accounting @end direntry @documentencoding UTF-8 @iftex @finalout @end iftex @titlepage @title Ledger Mode @subtitle Emacs Support For Version 3.0 of Ledger @author Craig Earls @page @vskip 0pt plus 1filll @insertcopying @end titlepage @contents @ifnottex @node Top, Introduction to Ledger-mode, (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... @end ifnottex @menu * Introduction to Ledger-mode:: * The Ledger Buffer:: * The Reconcile Buffer:: * The Report Buffer:: * Scheduling Transactions:: * Customizing Ledger-mode:: * Generating Ledger Regression Tests:: * Embedding Example results in Ledger Documentation:: * Hacking Ledger-mode:: * Concept Index:: * Command & Variable Index:: * Keystroke Index:: @end menu @node Introduction to Ledger-mode, The Ledger Buffer, Top, 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 @cindex 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}, or @file{~/.Aquamacs/Preferences.el}). @lisp (autoload 'ledger-mode "ledger-mode" "A major mode for Ledger" t) (add-to-list 'load-path (expand-file-name "/path/to/ledger/source/lisp/")) (add-to-list 'auto-mode-alist '("\\.ledger$" . ledger-mode)) @end lisp 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 @cindex menu 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 @cindex 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 @kindex C-c TAB @kindex C-c C-a 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 @kbd{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 @command{xact} command, by either typing @kbd{C-c C-a} or using @samp{Add Transaction} menu entry. Then typing a close match to the payee. Ledger-mode will call @command{ledger xact} with the data you enter and place the transaction in the proper chronological place in the ledger. If you need to add a lot of transactions that are not near your current date you can set the current year and month so that using @samp{Add Transaction} will prompt you with a more convenient month and year. To set the month type @kbd{C-c RET} and enter the month you want. @kbd{C-c C-y} will prompt you for the year. These settings only effect the @samp{Add Transaction} command. @node Reconciliation, Reports, Quick Add, Quick Demo @subsection Reconciliation @kindex C-c C-r @kindex SPC @kindex C-c C-c @kindex q The biggest task of maintaining a ledger is ensuring that 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 @kbd{C-c C-r}. If cursor is on an account, Ledger-mode will propose this account, or in the Minibuffer, will prompt for an account to reconcile. Hit @kbd{RET} if you are happy with proposed account, or enter @samp{Checking} as example. 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 @samp{1710}. Note that Ledger-mode assumes your are using @samp{$} (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 @kbd{SPC} 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 @samp{$0}. End the reconciliation by typing @kbd{C-c C-c}. This saves the @file{demo.ledger} buffer and marks the transactions and finally cleared. Type @kbd{q} to close out the reconciliation buffer. @node Reports, Narrowing, Reconciliation, Quick Demo @subsection Reports @kindex C-c C-o C-r @kindex C-c C-c 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 @kbd{C-c C-o C-r}. In the Minibuffer 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 Minibuffer type @samp{account}. When prompted for an account type @samp{checking}. In a buffer named @file{*Ledger Report*}, you will see a Ledger register report. You can move around the buffer, with the point on a transaction, type @kbd{RET}. 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 @kbd{C-c C-o C-r}. When prompted for a report to run, type @samp{bal}, and a balance report of all accounts will be shown. @node Narrowing, , Reports, Quick Demo @subsection Narrowing @kindex C-c C-f @kindex C-c C-g 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 @command{occur} mode functionality. Typing @kbd{C-c C-f} and entering any regex in the Minibuffer will show only transactions that match the regex. The regex can be on any field, or amount. Use @kbd{C-c C-g} after editing transactions to re-apply the current regex. Cancel the narrowing by typing @kbd{C-c C-f} again. @node The Ledger Buffer, The Reconcile Buffer, Introduction to Ledger-mode, Top @chapter The Ledger Buffer @menu * Adding Transactions:: * Copying Transactions:: * Editing Amounts:: * Marking Transactions:: * Formatting Transactions:: * Deleting Transactions:: * Sorting Transactions:: * Narrowing Transactions:: @end menu @node Adding Transactions, Copying Transactions, The Ledger Buffer, The Ledger Buffer @section Adding Transactions @findex ledger-post-auto-adjust-amounts @findex ledger-post-amount-alignment-column @kindex TAB @cindex transaction, adding Beyond the two ways of quickly adding transactions (@pxref{Quick Add}) Ledger-mode assists you by providing robust @kbd{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 @kbd{TAB} will cycle through the possible completions. Ledger-mode can also help you keep your amounts aligned. Setting @option{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 @option{ledger-post-amount-alignment-column}, which defaults to @samp{52}. @xref{Ledger Post Customization Group}. @menu * Setting a Transactions Effective Date:: * Quick Balance Display:: @end menu @node Setting a Transactions Effective Date, Quick Balance Display, Adding Transactions, Adding Transactions @subsection Setting a Transactions Effective Date @kindex C-c C-t @cindex effective date Ledger provides for adding information to a transaction that add details to the dates. For example, you can specify when the transaction was entered, when the transaction was cleared, or when individual postings were cleared. Ledger-mode refers to these additional dates as @emph{effective} dates. To set the effective date of a transaction, place the point in the first line of a transaction and type @kbd{C-c C-t}. The effective date will be added to the transaction. To set the effective date for an individual posting, place point in the posting and type @kbd{C-c C-t} and the effective date for that posting will be added at the end of the posting. @node Quick Balance Display, , Setting a Transactions Effective Date, Adding Transactions @subsection Quick Balance Display @kindex C-c C-p @cindex balance 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 @kbd{C-c C-p}. The Minibuffer will ask you to verify the name of the account you want, if it is already correct hit @kbd{RET}, then the balance of the account will be displayed in the Minibuffer. @node Copying Transactions, Editing Amounts, Adding Transactions, The Ledger Buffer @section Copying Transactions @kindex C-c C-k @cindex transaction, copying An easy way to copy a transaction is to type @kbd{C-c C-k} or menu entry @samp{Copy Trans at Point}. You will be prompted the new date for the copied transaction, and after having confirmed with @kbd{RET}, new transaction will be inserted at @emph{date} position in buffer. @node Editing Amounts, Marking Transactions, Copying Transactions, The Ledger Buffer @section Editing Amounts @kindex C-c C-b @kindex y @cindex Calc @cindex GNU Emacs Calculator @cindex transaction, editing amounts GNU Emacs Calculator, aka @samp{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 @command{Calc}. With the point anywhere in the same line as a posting, typing @kbd{C-c C-b} will bring up the @file{Calc} buffer, and push the current amount for the posting onto the top of the @command{Calc} stack. Perform any calculations you need to arrive at the final value, then type @kbd{y} to yank the value at the top of stack back into the ledger buffer. Note: @command{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 @command{Calc}, but it cannot intercept the value being yanked form the @command{Calc} stack, so decimal-comma users will have to manually replace the period with a comma. @node Marking Transactions, Formatting Transactions, Editing Amounts, The Ledger Buffer @section Marking Transactions @cindex transaction, marking @cindex uncleared @cindex pending @cindex cleared 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 @emph{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 @kindex C-c C-c @kindex C-c C-e Typing @kbd{C-c C-c}, depending where is the point, will clear the complete transaction, or an individual posting. This places an asterisk @samp{*} prior to the payee for the complete transaction, or prior to the account for an individual posting. When point is inside a transaction, specifically on an individual posting, you can still clear the complete transaction by typing @kbd{C-c C-e}. @node Formatting Transactions, Deleting Transactions, Marking Transactions, The Ledger Buffer @section Formatting Transactions @cindex transaction, formatting When editing a transaction, liberal use of the @kbd{TAB} key can keep the transaction well formatted. If you want to have Ledger-mode cleanup the formatting of a transaction you can use @samp{Align Transaction} or @samp{Align Region} from the menu bar. The menu item @samp{Clean-up Buffer} sorts all transactions in the buffer by date, removes extraneous empty lines and aligns every transaction. @node Deleting Transactions, Sorting Transactions, Formatting Transactions, The Ledger Buffer @section Deleting Transactions @kindex C-c C-d @cindex transaction, deleting Along with normal buffer editing methods to delete text, Ledger-mode provides an easy way to delete the transaction under point: @kbd{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 @kindex C-c C-s @cindex transaction, sorting 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. Either using @samp{Sort Region} menu entry or typing @kbd{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 @samp{YYYY/MM/DD} which easily sorts. Note, there is a menu entry @samp{Sort Buffer} 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 example: @example <<< information to not sort >>> ; Ledger-mode: Start sort <<< transactions to sort >>> ; Ledger-mode: End sort <<< information to not sort >>> @end example You can use menu entries @samp{Mark Sort Beginning} to insert start and @samp{Mark Sort End} to insert 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 @kindex C-c C-f @kindex C-c C-g @cindex transaction, narrowing @cindex transaction, display filtering 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 @emph{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 @samp{.37}, you can do that (I don't know why, but hey, whatever ever floats you aerostat). Using @kbd{C-c C-f} or the @samp{Narrow to Regex} menu entry, enter a regular expression in the Minibuffer. Ledger-mode will hide all other transactions. For details of the regular expression syntax, see your Emacs documentation. A few examples using the @file{demo.ledger} are given here: @table @samp @item Groceries Show only transactions that have a posting to the @samp{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. @samp{auto} @item harley$ Show only transactions with any line ending with @samp{harley}. @end table To show back all transactions simply invoke @samp{Narrow to Regex} or @kbd{C-c C-f} again. If you've edited some transactions after narrowing such that they would no longer match the regular expression, you can refresh the narrowed view using @kbd{C-c C-g}. @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 @cindex reconciliation, basics 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 @emph{clear}. That is the root of the difference between @emph{obligating} funds and @emph{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 @findex ledger-reconcile-default-commodity @kindex C-c C-r @cindex reconciliation, starting 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 on-line transaction history. It also helps immensely to know the final cleared balance you are aiming for. Use menu @samp{Reconcile Account} or keyboard shortcut @kbd{C-c C-r} to start reconciliation. If cursor is on an account, Ledger-mode will propose this account, or in the Minibuffer, will prompt for an account to reconcile. Hit @kbd{RET} if you are happy with proposed account, or enter @samp{Checking} as example. Ledger-mode is not particular about what you enter for the account. You can leave it blank and @file{*Reconcile*} buffer will show you @emph{all} uncleared transactions. After you enter the account enter the target amount. It is helpful to enter an amount with a commodity. You can also leave it blank, you will be able to clear transactions but not benefit from balance calculations. It assumes initially that you are using @samp{$} (USD) as your default commodity. If you are working in a different currency you can change the default in variable @option{ledger-reconcile-default-commodity} to whatever you need. If you work in multiple commodities simply enter the commoditized amount (for example @samp{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 @kindex SPC @cindex reconciliation, transaction marking 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 @kbd{SPC} 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 @kindex RET @kindex C-c C-c @cindex reconciliation, transaction editing If you find errors during reconciliation. You can visit the transaction under point in the @file{*Reconcile*} buffer by hitting the @kbd{RET} 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 @cindex reconciliation, finalizing @kindex C-c C-c @kindex q Once you have marked all transactions as pending and the cleared balance is correct. Finish the reconciliation by typing @kbd{C-c C-c}. This marks all pending transactions as cleared and saves the ledger buffer. Type @kbd{q} to close out the reconciliation buffer. If variable @var{ledger-reconcile-finish-force-quit} is set, the reconciliation buffer will be killed automatically after @kbd{C-c C-c}. @node Adding and Deleting Transactions during Reconciliation, Changing Reconciliation Account, Finalize Reconciliation, The Reconcile Buffer @section Adding and Deleting Transactions during Reconciliation @kindex a @kindex d @cindex reconciliation, transaction adding and deleting While reconciling, you may find new transactions that need to be entered into your ledger. Simply type @kbd{a} to bring up the quick add for the ledger buffer. Typing @kbd{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 @kindex g @cindex reconciliation, account changing You can conveniently switch the account being reconciled by typing @kbd{g}, and entering a new account to reconcile. This simply restarts the reconcile process. Any transactions that were marked @emph{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 @kindex t @cindex reconciliation, target changing If for some reason during reconciliation your target amount changes, type @kbd{t} and enter the new target value. @node The Report Buffer, Scheduling Transactions, 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 @kindex C-c C-o C-r @kindex C-c C-o C-g @kindex C-c C-o C-a @cindex report, running 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 @kbd{C-c C-o C-r} or using menu @samp{Run Report} prompts for the name of a saved report. The built-in reports are: @table @var @item bal Produce a balance reports of all accounts. @item reg Produce a register report of all transactions. @item payee Prompt for a payee, then produce a register report of all transactions involving that payee. @item account Prompt for an account, then produce a register report of all transactions involving that account. @end table While viewing reports you can easily switch back and forth between the ledger buffer and the @file{*Ledger Report*} buffer. In @file{*Ledger Report*} buffer, typing @kbd{RET} will take you to that transaction in the ledger buffer. While in the ledger buffer @kbd{C-c C-o C-g} returns you to the @file{*Ledger Report*} buffer. By default Ledger-mode will refresh the report buffer when the ledger buffer is saved. If you want to rerun the report at another time @kbd{C-c C-o C-a}. This is useful if you have other programs altering your ledger file outside of Emacs. @node Adding and Editing Reports, Reversing Report Order, Running Basic Reports, The Report Buffer @section Adding and Editing Reports @findex ledger-reports @kindex M-1 C-c C-o C-r @kindex S @kindex C-c C-o C-e @kindex e @cindex report, adding and editing @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 @kbd{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 @kbd{RET}, the report will be run, but it will not be permanently saved. If you want to save it, type @kbd{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 @kbd{C-c C-o C-e} or using @samp{Edit Report} menu in the ledger buffer, or typing @kbd{e} in the @file{*Ledger Report*} buffer. This takes you to the Emacs customization window for the Ledger Reports variables. Use the widgets to delete the report you want removed. Typing @kbd{C-c C-o C-s} will prompt for a name and save the current report. @node Expansion Formats, Make Report Transactions Active, Adding and Editing Reports, Adding and Editing Reports @subsection Expansion Formats @cindex report, custom variable It is sometimes 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 entered at runtime by the user. The built-in report @var{account} does exactly that, using a variable expansion to prompt the user for the account to use. There are four variables that can be expanded to run a report: @table @var @item ledger-file Returns the file to be operated on. @item payee Prompts for a payee. @item account Prompt for an account. @item tagname Prompt for a meta-data tag name. @item tagvalue Prompt for a meta-data 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: @example ledger -f %(ledger-file) reg %(account) \ --limit \"tag('my-tag') =~/%(value)/\" @end example 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 @cindex report, custom command 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 @option{--subtotal} option. It does this by adding @option{--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 @kindex R @cindex report, order reversing Often, banks show their on-line 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 @kbd{R} in the @file{*Ledger Report*} buffer and it will reverse the order of the transactions and maintain the proper mathematical sense. @node Scheduling Transactions, Customizing Ledger-mode, The Report Buffer, Top @chapter Scheduling Transactions The Ledger program provides for automating transactions but these transaction aren't @emph{real}, they only exist inside a ledger session and are not reflected in the actual data file. Many transactions are very repetitive, but may vary slightly in the date they occur on, or the amount. Some transactions are weekly, monthly, quarterly or annually. Ledger mode provides a way to schedule upcoming transaction with a flexible scheduler that allows you to specify the transactions in a separate ledger file and calculate the upcoming occurrences of those transactions. You can then copy the transactions into your live data file. @menu * Specifying Upcoming Transactions:: @end menu @node Specifying Upcoming Transactions, , Scheduling Transactions, Scheduling Transactions @section Specifying Upcoming Transactions The format for specifying transactions is identical to Ledger's file format with the exception of the date field. The data field is modified by surrounding it with brackets and using wild cards and special characters to specify when the transactions should appear. @menu * Transactions that occur on specific dates:: * Transactions that occur on specific days:: @end menu @node Transactions that occur on specific dates, Transactions that occur on specific days, Specifying Upcoming Transactions, Specifying Upcoming Transactions @subsection Transactions that occur on specific dates Many times you will enter repetitive transactions that occur on the same day of the month each month. These can be specified using a wild card in the year and month with a fixed date in the day. The following entry specifies a transaction that occurs on the first and fifteenth of every month in every year. @example [*/*/1,15] Paycheck Income:Job $1000.00 Assets:Checking @end example Some transactions do not occur every month. Comma separated lists of the months, or @samp{E} for even, or @samp{O} for odd number months can also be specified. The following entry specifies a bi-monthly exterminator bill that occurs in the even months: @example [*/E/01] Exterminator Expenses:Home $100.00 Assets:Checking @end example @node Transactions that occur on specific days, , Transactions that occur on specific dates, Specifying Upcoming Transactions @subsection Transactions that occur on specific days Some transactions occur every relative to the day of the week rather than the date of the month. For example, many people are paid every two weeks without regard to the day of the month. Other events may occur on specific days regardless of the date. For example the following transactions creates a transaction every other Thursday: @example [2014/11/27+2Th] Paycheck Income:Job $1000.00 Assets:Checking @end example It is necessary to specify a starting date in order for this type of recurrence relation to be specified. The day names are two character codes that default to Mo, Tu, We, Th, Fr, Sa, Su, for Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday respectively. You can change the codes to something more convenient for your locale by customizing the ledger @option{ledger-schedule-week-days}. They must be two characters long. @node Customizing Ledger-mode, Generating Ledger Regression Tests, Scheduling Transactions, 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 @samp{Customize Specific Group} and enter @samp{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 @cindex customization, ledger-mode @ftable @option @item ledger-occur-use-face-shown If non-nil, use a custom face for transactions shown in @option{ledger-occur} mode using @option{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 transaction under point using @option{ledger-font-highlight-face}. @end ftable @node Ledger Reconcile Customization Group, Ledger Report Customization Group, Ledger Customization Group, Customization Variables @subsection Ledger Reconcile Customization Group @cindex customization, reconcile @ftable @option @item ledger-recon-buffer-name Name to use for reconciliation buffer. Defaults to @file{*Reconcile*}. @item ledger-narrow-on-reconcile If t, limit transactions shown in main buffer to those matching the reconcile regex. @item ledger-buffer-tracks-reconcile-buffer If t, then when the cursor is moved to a new transaction in the @file{*Reconcile*} buffer. Then that transaction will be shown in its source buffer. @item ledger-reconcile-force-window-bottom If t, make the @file{*Reconcile*} window appear along the bottom of the register window and resize. @item ledger-reconcile-toggle-to-pending If t, then toggle between uncleared and pending @samp{!}. If false toggle between uncleared and cleared @samp{*}. @item ledger-reconcile-default-date-format Date format for the reconcile buffer. Defaults to @option{ledger-default-date-format}. @item ledger-reconcile-target-prompt-string Prompt for recon target. Defaults to "Target amount for reconciliation ". @item ledger-reconcile-buffer-header Header string for the reconcile buffer. If non-nil, the name of the account being reconciled will be substituted into the '%s'. If nil, no header will be displayed. Defaults to "Reconciling account %s\n\n". @item ledger-reconcile-buffer-line-format Format string for the ledger reconcile posting format. Available fields are date, status, code, payee, account, amount. The format for each field is %WIDTH(FIELD), WIDTH can be preceded by a minus sign which mean to left justify and pad the field. WIDTH is the minimum number of characters to display; if string is longer, it is not truncated unless @option{ledger-reconcile-buffer-payee-max-chars} or @option{ledger-reconcile-buffer-account-max-chars} is defined. Defaults to "%(date)s %-4(code)s %-50(payee)s %-30(account)s %15(amount)s\n" @item ledger-reconcile-buffer-payee-max-chars If positive, truncate payee name right side to max number of characters. @item ledger-reconcile-buffer-account-max-chars If positive, truncate account name left side to max number of characters. @item ledger-reconcile-sort-key Key for sorting reconcile buffer. Possible values are '(date)', '(amount)', '(payee)' or '(0)' for no sorting, i.e. using ledger file order. Defaults to '(0)'. @item ledger-reconcile-insert-effective-date nil If t, prompt for effective date when clearing transactions during reconciliation. @item ledger-reconcile-finish-force-quit nil If t, will force closing reconcile window after @kbd{C-c C-c}. @end ftable @node Ledger Report Customization Group, Ledger Faces Customization Group, Ledger Reconcile Customization Group, Customization Variables @subsection Ledger Report Customization Group @cindex customization, report @ftable @option @item ledger-reports Definition of reports to run. @item ledger-report-format-specifiers An alist mapping ledger report format specifiers to implementing functions. @end ftable @node Ledger Faces Customization Group, Ledger Post Customization Group, Ledger Report Customization Group, Customization Variables @subsection Ledger Faces Customization Group @cindex customization, faces Ledger Faces: Ledger-mode highlighting @ftable @option @item ledger-font-uncleared-face Default face for Ledger. @item ledger-font-cleared-face Default face for cleared @samp{*} transactions. @item ledger-font-highlight-face Default face for transaction under point. @item ledger-font-pending-face Default face for pending @samp{!} 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 @file{*Reconcile*} buffer. @item ledger-font-reconciler-cleared-face Default face for cleared @samp{*} transactions in the @file{*Reconcile*} buffer. @item ledger-font-reconciler-pending-face Default face for pending @samp{!} transactions in the @file{*Reconcile*} buffer. @item ledger-font-report-clickable-face FIXME @end ftable @node Ledger Post Customization Group, Ledger Exec Customization Group, Ledger Faces Customization Group, Customization Variables @subsection Ledger Post Customization Group @cindex customization, post Ledger Post: @ftable @option @item ledger-post-auto-adjust-amounts If non-nil, then automatically align amounts to column specified in @option{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: @var{iswitchb}, @var{ido}, or built-in. @item ledger-post-use-ido @end ftable @node Ledger Exec Customization Group, Ledger Test Customization Group, Ledger Post Customization Group, Customization Variables @subsection Ledger Exec Customization Group @cindex customization, executable Ledger Exec: Interface to the Ledger command-line accounting program. @ftable @option @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 ftable @node Ledger Test Customization Group, Ledger Texi Customization Group, Ledger Exec Customization Group, Customization Variables @subsection Ledger Test Customization Group @cindex customization, test @ftable @option @item ledger-source-directory Directory where the Ledger sources are located. @item ledger-test-binary Directory where the debug binary. @end ftable @node Ledger Texi Customization Group, , Ledger Test Customization Group, Customization Variables @subsection Ledger Texi Customization Group @cindex customization, texi @ftable @option @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 @samp{--args-only --columns 80}. @end ftable @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, Concept Index, Embedding Example results in Ledger Documentation, Top @chapter Hacking Ledger-mode Work in Progress. @node Concept Index, Command & Variable Index, Hacking Ledger-mode, Top @unnumbered Concept Index @printindex cp @node Command & Variable Index, Keystroke Index, Concept Index, Top @unnumbered Command & Variable Index @printindex fn @node Keystroke Index, , Command & Variable Index, Top @unnumbered Keystroke Index @printindex ky @bye @c Local Variables: @c mode: texinfo @c TeX-master: t @c End: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/ledger.1���������������������������������������������������������������������0000664�0000000�0000000�00000115712�12660234410�0016054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.Dd March 23, 2012 .Dt LEDGER 1 .Os .Sh NAME .Nm ledger .Nd Command-line, double-entry account reporting tool .Sh SYNOPSIS .Nm .Op Ar command .Op Ar options .Op Ar arguments .Sh DESCRIPTION .Nm is a command-line accounting tool based on the power and completeness of double-entry accounting. It is only a reporting tool, which means it never modifies your data files, but it does offer a large selection of reports, and different ways to customize them to your needs. .Sh COMMANDS .Nm accepts several top-level commands, each of which generates a different kind of basic report. Most of them accept a .Ar report-query argument, in order to determine what should be reported. To understand the syntax of a .Ar report-query , see the section on .Sx QUERIES . In its most basic form, simply specifying one or more strings produces a report for all accounts containing those strings. .Pp If no command is given, .Nm enters a .Tn REPL , or command loop, allowing several commands to be executed on the same dataset without reparsing. .Pp The following is a complete list of accepted reporting commands: .Bl -tag -width accounts .It Ic accounts Oo Ar report-query Oc List all accounts for postings that match the .Ar report-query . .El .Bl -tag -width balance .It Ic balance Oo Ar report-query Oc Print a balance report showing totals for postings that match .Ar report-query , and aggregate totals for parents of those accounts. Options most commonly used with this command are: .Bl -tag -compact -width "--collapse (-n)" .It Fl \-basis Pq Fl B Report in terms of cost basis, not amount or value. This is the only form of report which is guaranteed to always balance to zero, when no .Ar report-query is specified. Only show totals for the top-most accounts. .It Fl \-empty Pq Fl E Show accounts whose total is zero. .It Fl \-flat Rather than display a hierarchical tree, flatten the report to show subtotals for only accounts matching .Ar report-query . .It Fl \-no-total Suppress the summary total shown at the bottom of the report. .El .Pp The synonyms .Ic bal and .Ic b are also accepted. .It Ic budget Oo Ar report-query Oc A special balance report which includes three extra columns: the amount budgeted during the reporting period, how spending differed from the budget, and the percentage of budget spent (exceeds 100% if you go over budget). Note that budgeting requires one or more .Do periodic transactions .Dc to be defined in your data file(s). See the manual for more information. .It Ic cleared Oo Ar report-query Oc A special balance report which adds two extra columns: the cleared balance for each account, and the date of the most recent cleared posting in that account. For this accounting to be meaningful, the cleared flag must be set on at least one posting. See the manual for more information. .It Ic commodities Oo Ar report-query Oc List all commodities for postings matching the .Ar report-query . .It Ic convert Reads data from a CSV (comma-separated values) file and generates .Nm transactions. .It Ic csv Oo Ar report-query Oc 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 Ic entry Oo Ar entry-template Oc Generate and display a new, properly formatted .Nm transaction by comparing the .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 ENTRIES . .Pp The synonym .Ic xact is also accepted. .It Ic emacs Oo Ar query Oc Output posting and transaction data in a format readily consumed by the Emacs editor, in a series of Lisp forms. This is used by the .Pa ledger.el Emacs mode to process reporting data from .Nm . .It Ic equity Oo Ar report-query Oc Print a transaction with a series of postings that balance current totals for accounts matching the .Ar report-query in a special account called .Li Equity:Opening Balances . The purpose of this report is to close the books for a prior year, while using these equity postings to carry forward those balances. .It Ic org Produce a journal file suitable for use in the Emacs org mode. .It Ic payees Oo Ar report-query Oc List all payees for postings matching the .Ar report-query . .It Ic pricemap Produce a file which can be used to generate a graph with graphviz showing the relationship of commodities in the .Nm file. .It Ic prices Oo Ar report-query Oc Report prices for all commodities in postings matching the .Ar report-query . The prices are reported with the granularity of a single day. .It Ic pricedb Oo Ar report-query Oc Report prices for all commodities in postings matching the .Ar report-query . Prices are reported down to the second, using the same format as the .Pa ~/.pricedb file. .It Ic print Oo Ar report-query Oc Print out the full transactions of any matching postings using the same format as they would appear in a data file. This can be used to extract subsets from a .Nm file to transfer to other files. .It Ic push Oo Ar options Oc In the .Tn REPL , push a set of command-line .Ar options , so that they will apply to all subsequent reports. .It Ic pop In the .Tn REPL , pop any option settings that have been .Sm off .Ic push ed. .Sm on .It Ic register Oo Ar report-query Oc List all postings matching the .Ar report-query . This is one of the most common commands, and can be used to provide a variety of useful reports. Options most commonly used with this command are: .Pp .Bl -tag -compact -width "--collapse (-n)" .It Fl \-average Pq Fl A Show the running average, rather than a running total. .It Fl \-current Pq Fl c Don't show postings beyond the present day. .It Fl \-exchange Ar commodity Pq Fl X Render all values in the given .Ar commodity , if a price conversion rate can be determined. Rates are always displayed relative to the date of the posting they are calculated for. This means a .Ic register report is a historical value report. For current values, it may be preferable to use the .Ic balance report. .It Fl \-gain Pq Fl G Show any gains (or losses) in commodity values over time. .It Fl \-head Ar number Only show the top .Ar number postings. .It Fl \-historical Pq Fl H Value commodities at the time of their acquisition. .It Fl \-invert Invert the value of amounts shown. .It Fl \-market Pq Fl V Show current market values for all amounts. This is determined in a somewhat magical fashion. It is probably more straightforward to use .Fl \-exchange option. .It Fl \-period Ar time-period Pq Fl p Show postings only for the given .Ar time-period . .It Fl \-related Pq Fl r Show postings that are related to those that would have been shown. It has the effect of displaying the .Qq other side of the postings. .It Fl \-sort Ar value-expression Pq Fl S Sort postings by evaluating the given .Ar value-expression . Note that a comma-separated list of expressions is allowed, in which case each sorting term is used in order to determine the final ordering. For example, to search by date and then amount, one would use: .Dl ledger reg --sort 'date, amount' .It Fl \-tail Ar number Only show the last .Ar number postings. .It Fl \-uncleared Pq Fl U Only show uncleared (i.e., recent) postings. .El .Pp There are also several grouping options that can be useful: .Pp .Bl -tag -compact -width "--collapse (-n)" .It Fl \-by-payee Pq Fl P Group postings by common payee names. .It Fl \-daily Pq Fl D Group postings by day. .It Fl \-weekly Pq Fl W Group postings by week (starting on Sundays). .It Fl \-start-of-week Ar day Set the start of each report grouped by week to the given .Ar day . .It Fl \-monthly Pq Fl M Group postings by month. .It Fl \-quarterly Group postings by fiscal quarter. .It Fl \-yearly Pq Fl Y Group postings by year. .It Fl \-days-of-week Group postings by the day of the week on which they took place. .It Fl \-subtotal Pq Fl s Group all postings together. This is very similar to the totals shown by the .Ic balance report. .El .Pp The synonyms .Ic reg and .Ic r are also accepted. .It Ic server This command requires that Python support be active. If so, it starts up an .Tn HTTP server listening for requests on port 9000. This provides an alternate interface to creating and viewing reports. Note that this is very much a work-in-progress, and will not be fully functional until a later version. .It Ic select Oo Ar sql-query Oc List all postings matching the .Ar sql-query . This command allows to generate SQL-like queries, e.g.: .Dl Li ledger select date,amount from posts where account=~/Income/ .It Ic source Parse a journal file and checks it for errors. .Nm will return success if no errors are found. .It Ic stats Oo Ar report-query Oc Provide summary information about all the postings matching .Ar report-query . It provides information such as: .Bl -bullet -offset indent -compact .It Time range of all matching postings .It Unique payees .It Unique accounts .It Postings total .It Uncleared postings .It Days since last posting .It Posts in the last 7 days .It Posts in the last 30 days .It Posts this month .El .It Ic xml Oo Ar report-query Oc Output data relating to the current report in .Tn XML format. It includes all accounts and commodities involved in the report, plus the postings and the transactions they are contained in. See the manual for more information. .El .Sh OPTIONS .Bl -tag -width -indent .It Fl \-abbrev-len Ar INT Set the minimum length an account can be abbreviated to if it doesn't fit inside the .Sy account-width . If .Ar INT is zero, then the account name will be truncated on the right. If .Ar INT is greater than .Sy 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. .It Fl \-account Ar EXPR Prepend .Ar EXPR to all accounts reported. That is, the option .Fl \-account Ar \*q'Personal'\*q would tack .Ar Personal: and .Fl \-account Ar \*qtag('VAT')\*q would tack the value of the VAT tag to the beginning of every account reported in a .Ic balance or .Ic register report. .It Fl \-account-width Ar INT Set the width of the account column in the .Ic register report to .Ar INT characters. .It Fl \-actual Pq Fl L Report only real transactions, with no automated or virtual transactions used. .It Fl \-add-budget Show only un-budgeted postings. .It Fl \-amount Ar EXPR Pq Fl t Apply the given value expression to the posting amount. Using .Fl \-amount Ar EXPR you can apply an arbitrary transformation to the postings. .It Fl \-amount-data Pq Fl j On a register report print only the dates and amount of postings. Useful for graphing and spreadsheet applications. .It Fl \-amount-width Ar INT Set the width in characters of the amount column in the .Ic register report. .It Fl \-anon Anonymize registry output, mostly for sending in bug reports. .It Fl \-ansi Use color if the terminal supports it. Alias for .Fl \-color .It Fl \-args-only Ignore init files and environment variables for the .Nm run. .It Fl \-auto-match When generating a ledger transaction from a CSV file using the .Ic convert command, automatically match an account from the Ledger journal. .It Fl \-aux-date Show auxiliary dates for all calculations. Alias for .Fl \-effective .It Fl \-average Pq Fl A Print average values over the number of transactions instead of running totals. .It Fl \-balance-format Ar FMT Specify the format to use for the .Ic balance report. .It Fl \-base Reduce convertible commodities down the bottom of the conversion, e.g. display time in seconds. .It Fl \-basis Pq Fl B Report the cost basis on all posting. Alias for .Fl \-cost .It Fl \-begin Ar DATE Pq Fl b Specify the start .Ar DATE of all calculations. Transactions before that date will be ignored. .It Fl \-bold-if Ar EXPR Print the entire line in bold if the given value expression is true. .It Fl \-budget Only display budgeted items. In a .Ic register report this displays transaction in the budget, in a balance report this displays accounts in the budget. .It Fl \-budget-format Ar FMT Specify the format to use for the .Ic budget report. .It Fl \-by-payee Pq Fl P Group postings in the register report by common payee names. .It Fl \-check-payees Enable strict and pedantic checking for payees as well as accounts, commodities and tags. .It Fl \-cleared Pq Fl C Display only cleared postings. .It Fl \-cleared-format Ar FMT Specify the format to use for the .Ic cleared report .It Fl \-collapse Pq Fl n Print only the top level accounts. .It Fl \-collapse-if-zero Collapse the account display only if it has a zero balance. .It Fl \-color Use color if the terminal supports it. Alias for .Fl \-ansi .It Fl \-columns Ar INT Make the .Ic register report .Ar INT characters wide. By default .Nm will use all available columns in your terminal. .It Fl \-cost Report the cost basis on all posting. Alias for .Fl \-basis . .It Fl \-count Direct .Nm to report the number of items when appended to the .Ic commodities , .Ic accounts or .Ic payees commands. .It Fl \-csv-format Ar FMT Format .Ic csv report according to .Ar FMT . .It Fl \-current Pq Fl c Shorthand for .Fl \-limit Ar "'date <= today'" . .It Fl \-daily Pq Fl D Shorthand for .Fl \-period Ar daily . .It Fl \-date Ar EXPR Transform the date of the transaction using .Ar EXPR . .It Fl \-date-format Ar DATEFMT Pq Fl y Print dates using .Ar DATEFMT . Refer to .Xr strftime 3 for details on the format string syntax. .It Fl \-datetime-format Ar DATETIMEFMT Print datetimes using .Ar DATETIMEFMT . Refer to .Xr strftime 3 for details on the format string syntax. .It Fl \-date-width Ar INT Specify the width, in characters, of the date column in the .Ic register report. .It Fl \-day-break Break up .Ic register report of timelog entries that span multiple days by day. .It Fl \-days-of-week Group transactions by the days of the week. Alias for .Fl \-dow . .It Fl \-dc Display register or balance in debit/credit format If you use .Fl \-dc with either the .Ic register or .Ic balance commands, you will now get separate columns for debits and credits. .It Fl \-debug Ar STR If .Nm has been built with debug options this will provide extra data during the run. .It Fl \-decimal-comma Direct .Nm to parse journals using the European standard comma as decimal separator, vice a period. .It Fl \-depth Ar INT Limit the depth of the account tree. In a balance report, for example, .Fl \-depth Ar 2 will print balances only for accounts with two levels, i.e. .Sy Expenses:Entertainment but not .Sy Expenses:Entertainment:Dining . This is a display predicate, which means it only affects display, not the total calculations. .It Fl \-detail Related to .Ic convert command. Synonym to .Fl \-rich-data option. .It Fl \-deviation Report each posting's deviation from the average. It is only meaningful in the .Ic register No and Ic prices reports. .It Fl \-display Ar EXPR Pq Fl d Display lines that satisfy the expression .Ar EXPR . .It Fl \-display-amount Ar EXPR Apply a transformation to the .Em displayed amount. This occurs after calculations occur. .It Fl \-display-total Ar EXPR Apply a transformation to the .Em displayed total. This occurs after calculations occur. .It Fl \-dow Group transactions by the days of the week. Alias for .Fl \-days-of-week . .It Fl \-download Cause quotes to be automagically downloaded, as needed, by running a script named .Em getquote and expecting that script to return a value understood by .Nm . A sample implementation of a .Em 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 .Ev LEDGER_PRICE_DB . .It Fl \-effective Show auxiliary dates for all calculations. Alias for .Fl \-aux-date . .It Fl \-empty Pq Fl E Include empty accounts in report. .It Fl \-end Ar DATE Pq Fl e Constrain the report so that transactions on or after .Ar DATE are not considered. .It Fl \-equity Related to the .Ic equity command. Gives current account balances in the form of a register report. .It Fl \-exact Report beginning and ending of periods by the date of the first and last posting occurring in that period. .It Fl \-exchange Ar COMMODITY Oo , Ar COMMODITY, ... Oc Pq Fl X Display values in terms of the given .Ar COMMODITY . The latest available price is used. .It Fl \-explicit Direct .Nm to require pre-declarations for entities (such as accounts, commodities and tags) rather than taking entities from cleared transactions as defined. .It Fl \-file Ar FILE Read journal data from .Ar FILE . .It Fl \-first Ar INT Print the first .Ar INT entries. Opposite of .Fl \-last Ar INT . Alias for .Fl \-head . .It Fl \-flat Force the full names of accounts to be used in the balance report. The balance report will not use an indented tree. .It Fl \-force-color Output TTY color codes even if the TTY doesn't support them. Useful for TTYs that don't advertise their capabilities correctly. .It Fl \-force-pager Force .Nm to paginate its output. .It Fl \-forecast-while Ar EXPR Continue forecasting while .Ar VEXPR is true. Alias for .Fl \-forecast . .It Fl \-forecast-years Ar INT Forecast at most .Ar INT years into the future. .It Fl \-format Ar FMT Pq Fl F Use the given format string .Ar FMT to print output. .It Fl \-gain Pq Fl G Report net gain or loss for commodities that have a price history. .It Fl \-generated Include auto-generated postings (such as those from automated transactions) in the report, in cases where you normally wouldn't want them. .It Fl \-group-by Ar EXPR Group transaction together in the .Ic register report. .Ar EXPR can be anything, although most common would be .Ar payee or .Ar commodity . The .Fn tag function is also useful here. .It Fl \-group-title-format Ar FMT Set the format for the headers that separate reports section of a grouped report. Only has effect with a .Fl \-group-by Ar EXPR register report. .It Fl \-head Ar INT Print the first .Ar INT entries. Opposite of .Fl \-tail Ar INT . Alias for .Fl \-first .It Fl \-help Print this man page. .It Fl \-immediate Evaluate calculations immediately rather than lazily. .It Fl \-import Ar FILE Import .Ar FILE as Python module. .It Fl \-init-file Ar FILE Pq Fl i Read .Ar FILE before any other .Nm 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 its own line. .It Fl \-inject Ar STR Use .Ar STR amounts in calculations. In case you know what amount a transaction should be, but the actual transaction has the wrong value you can use metadata .Ar STR to specify the expected amount. .It Fl \-input-date-format Ar DATEFMT Specify the input date format for journal entries. .It Fl \-invert Change the sign of all reported values. .It Fl \-last Ar INT . Report only the last .Ar INT entries. Opposite of .Fl \-first Ar INT . Only useful on a register report. Alias for .Fl \-tail . .It Fl \-leeway Ar INT Pq Fl Z Alias for .Fl \-price-expr . .It Fl \-limit Ar EXPR Pq Fl l Limit postings in calculations. .It Fl \-lot-dates Report the date on which each commodity in a balance report was purchased. .It Fl \-lot-notes Report the tag attached to each commodity in a balance report. .It Fl \-lot-prices Report the price at which each commodity in a balance report was purchased. .It Fl \-lots Report the date and price at which each commodity was purchased in a balance report. .It Fl \-lots-actual Preserve the uniqueness of commodities so they aren't merged during reporting without printing the lot annotations. .It Fl \-market Pq Fl V Use the latest market value for all commodities. .It Fl \-master-account Ar STR Prepend all account names with .Ar STR .It Fl \-meta Ar STR In the register report, prepend the transaction with the value of the given tag .Ar STR . .It Fl \-meta-width Ar INT Specify the width of the Meta column used for the .Fl \-meta Ar TAG options. .It Fl \-monthly Pq Fl M Shorthand for .Fl \-period Ar monthly . .It Fl \-no-aliases Aliases are completely ignored. .It Fl \-no-color Suppress any color TTY output. .It Fl \-no-pager Disables the pager on TTY output. .It Fl \-no-revalued Stop .Nm from showing <Revalued> postings. .It Fl \-no-rounding Don't output .Qq Li <Adjustment> postings. Note that this will cause the running total to often not add up! Its main use is for .Fl \-amount-data Pq Fl j and .Fl \-total-data Pq Fl J reports. .It Fl \-no-titles Suppress the output of group titles. .It Fl \-no-total Suppress printing the final total line in a balance report. .It Fl \-now Ar DATE Use .Ar DATE as the current date. This affects the output when using .Fl \-period , .Fl \-begin , .Fl \-end , or .Fl \-current to decide which dates lie in the past or future. .It Fl \-only Ar EXPR This is a postings predicate that applies after certain transforms have been executed, such as periodic gathering. .It Fl \-options Display the options in effect for this .Nm invocation, along with their values and the source of those values. .It Fl \-output Ar FILE Pq Fl o Redirect the output of .Nm to .Ar FILE . .It Fl \-pager Ar STR Use .Ar STR as the pager program. .It Fl \-payee Sets a value expression for formatting the payee. In the .Ic register report this prevents the second entry from having a date and payee for each transaction. .It Fl \-payee-width Ar INT Set the number of columns dedicated to the payee in the register report to .Ar INT . .It Fl \-pedantic Accounts, tags or commodities not previously declared will cause errors. .It Fl \-pending Use only postings that are marked pending. .It Fl \-percent Pq Fl % Calculate the percentage value of each account in a balance reports. Only works for account that have a single commodity. .It Fl \-period Ar PERIOD Pq Fl p Define a period expression that sets the time period during which transactions are to be accounted. For a .Ic 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. .It Fl \-period-sort Sort the posting within transactions using the given value expression. .It Fl \-permissive Quiet balance assertions. .It Fl \-pivot Ar TAG Produce a balance pivot report .Qq around the given .Ar TAG . .It Fl \-plot-amount-format Ar FMT Define the output format for an amount data plot. .It Fl \-plot-total-format Ar FMT Define the output format for a total data plot. .It Fl \-prepend-format Ar FMT Prepend .Ar FMT to every line of the output. .It Fl \-prepend-width Ar INT Reserve .Ar INT spaces at the beginning of each line of the output. .It Fl \-price Pq Fl I Use the price of the commodity purchase for performing calculations. .It Fl \-price-db Ar FILE .It Fl \-price-exp Ar STR Pq Fl Z Set the expected freshness of price quotes, in .Ar INT minutes. That is, if the last known quote for any commodity is older than this value, and if .Fl \-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. Alias for .Fl \-leeway . .It Fl \-prices-format Ar FMT Set the format for the .Ic prices report. .It Fl \-pricedb-format Ar FMT Set the format expected for the historical price file. .It Fl \-primary-date Show primary dates for all calculations. Alias for .Fl \-actual-dates .It Fl \-quantity Pq Fl O Report commodity totals (this is the default). .It Fl \-quarterly Shorthand for .Fl \-period Ar quarterly . .It Fl \-raw In the .Ic 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. .It Fl \-real Pq Fl R Account using only real transactions ignoring virtual and automatic transactions. .It Fl \-recursive-aliases Causes .Nm to try to expand aliases recursively, i.e. try to expand the result of an earlier expansion again, until no more expansions apply. .It Fl \-register-format Ar FMT Define the output format for the .Ic register report. .It Fl \-related Pq Fl r In a register report show the related account. This is the other .Em side of the transaction. .It Fl \-related-all Show all postings in a transaction, similar to .Fl \-related but show both sides of each transaction. .It Fl \-revalued Report discrepancy in values for manual reports by inserting <Revalued> postings. This is implied when using the .Fl \-exchange Pq Fl X or .Fl \-market Pq Fl V option. .It Fl \-revalued-only Show only <Revalued> postings. .It Fl \-revalued-total Display the sum of the revalued postings as the running total, which serves to show unrealized capital in a gain/losses report. .It Fl \-rich-data When generating a ledger transaction from a CSV file using the .Ic convert command, add CSV, Imported, and UUID meta-data. .It Fl \-seed Ar INT Set the random seed to .Ar INT for the .Ic generate command. Used as part of development testing. .It Fl \-script Ar FILE Execute a .Nm script. .It Fl \-sort Ar EXPR Pq Fl S Sort the register report based on the value expression .Ar EXPR . .\".It Fl \-sort-all Ar EXPR .It Fl \-sort-xacts Sort the posting within transactions using the given value expression. .It Fl \-start-of-week Ar STR Use .Ar STR as the particular day of the week to start when using the .Fl \-weekly option. .Ar STR can be day names, their abbreviations like .Qq Mon , or the weekday number starting at 0 for Sunday. .It Fl \-strict Accounts, tags or commodities not previously declared will cause warnings. .It Fl \-subtotal Pq Fl s Report register as a single subtotal. .It Fl \-tail Ar INT Report only the last .Ar INT entries. Only useful on a register report. Alias for .Fl \-last Ar INT .It Fl \-time-colon Display the value for commodities based on seconds as hours and minutes. Thus 8100s will be displayed as 2:15h instead of 2.25h. .It Fl \-time-report Add two columns to the .Ic balance report to show the earliest checkin and checkout times for timelog entries. .It Fl \-total Ar EXPR Pq Fl T Define a value expression used to calculate the total in reports. .It Fl \-total-data Pq Fl J Show only dates and totals to format the output for plots. .It Fl \-total-width Ar INT Set the width of the total field in the register report. .It Fl \-trace Ar INT Enable tracing. The .Ar INT specifies the level of trace desired. .It Fl \-truncate Ar STR Indicates how truncation should happen when the contents of columns exceed their width. Valid arguments for .Ar STR are .Ar leading , .Ar middle , and .Ar trailing . The default is smarter than any of these three, as it considers sub-names within the account name (that style is called .Qq abbreviate ) . .It Fl \-unbudgeted Show only un-budgeted postings. .It Fl \-uncleared Pq Fl U Use only uncleared transactions in calculations and reports. .It Fl \-unrealized Show generated unrealized gain and loss accounts in the balance report. .It Fl \-unrealized-gains Allow the user to specify what account name should be used for unrealized gains. Defaults to .Sy "Equity:Unrealized Gains" . Often set in one's .Pa ~/.ledgerrc file to change the default. .It Fl \-unrealized-losses Allow the user to specify what account name should be used for unrealized losses. Defaults to .Sy "Equity:Unrealized Losses" . Often set in one's .Pa ~/.ledgerrc file to change the default. .It Fl \-unround Perform all calculations without rounding and display results to full precision. .It Fl \-values Show the values used by each tag when used in combination with the .Ic tags command. .It Fl \-value-expr Ar EXPR Set a global value expression annotation. .It Fl \-verbose Print detailed information on the execution of .Nm . .It Fl \-verify Enable additional assertions during run-time. This causes a significant slowdown. When combined with .Fl \-debug Ar CODE .Nm will produce memory trace information. .It Fl \-verify-memory Verify that every constructed object is properly destructed. This is for debugging purposes only. .It Fl \-version Print version information and exit. .It Fl \-weekly Pq Fl W Shorthand for .Fl \-period Ar weekly . .It Fl \-wide Pq Fl w Assume 132 columns instead of the TTY width. .It Fl \-yearly Pq Fl Y Shorthand for .Fl \-period Ar yearly . .El .Sh PRE-COMMANDS Pre-commands are useful when you aren't sure how a command or option will work. The difference between a pre-command and a regular command is that pre-commands ignore the journal data file completely, nor is the user's init file read. .Bl -tag -width -indent .It Ic args No / Ic query Evaluate the given arguments and report how .Nm interprets it against the following model transaction: .Bd -literal -offset indent 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 .Ed .It Ic eval Evaluate the given value expression against the model transaction. .It Ic format Print details of how .Nm uses the given formatting description and apply it against a model transaction. .It Ic parse No / Ic expr Print details of how .Nm uses the given value expression description and apply it against a model transaction. .It Ic generate Randomly generates syntactically valid .Nm data from a seed. Used by the GenerateTests harness for development testing. .It Ic period Evaluate the given period and report how .Nm interprets it. .\".It Ic script .It Ic template Shows the insertion template that the .Ic xact command generates. This is a debugging command. .El .Sh QUERIES The syntax for reporting queries can get somewhat complex. It is a series of query terms with an implicit OR operator between them. The following terms are accepted: .Bl -tag -width "term and term" .It Ar regex A bare string is taken as a regular expression matching the full account name. Thus, to report the current balance for all assets and liabilities, you would use: .Pp .Dl ledger bal asset liab .It Ic payee Ar regex Pq Ic \&@ Ns Ar regex Query on the payee, rather than the account. .It Ic tag Ar regex Pq Ic \&% Ns Ar regex .It Ic note Ar regex Pq Ic \&= Ns Ar regex Query on anything found in an item's note. .It Ic code Ar regex Pq Ic \&# Ns Ar regex Query on the xact's optional code (which can be any string the user wishes). .It Ar term Cm and Ar term Query terms are joined by an implicit OR operator. You can change this to AND by using the .Cm and keyword. For example, to show food expenditures occurring at Shakee's Pizza, you could say: .Pp .Dl Li ledger reg food and @Shakee .It Ar term Cm or Ar term When you wish to be more explicit, use the OR operator. .It Ic show .It Cm not Ar term Reverse the logical meaning of the following term. This can be used with parentheses to great effect: .Pp .Dl Li ledger reg food and @Shakee and not dining .It \&( Ar term No \&) If you wish to mix OR and AND operators, it is often helpful to surround logical units with parentheses. \fBNOTE\fR: Because of the way some shells interpret parentheses, you should always escape them: .Pp .Dl Li ledger bal \e( assets or liab \e) and not food .El .Sh EXPRESSIONS .Bl -tag -width "partial_account" .It Fn abs value Return the absolute value of the given .Ar value . .It Sy account Return the posting's account. .It Sy account_base Return the base account, i.e. everything after the last account delimiter ':'. .\".It Sy account_amount .It Sy actual .\" Is there a difference between real and actual? Return true if the transaction is real, i.e not a automated or virtual transaction, false otherwise. .It Sy amount Return the amount of the posting. .It Sy amount_expr Return the calculated amount of the posting according to the .Fl \-amount option. .It Fn ansify_if value color bool Render the given .Ar value as a string, applying the proper ANSI escape codes to display it in the given .Ar color if .Ar bool is true. It typically checks the value of the option .Fl \-color , for example: .Dl Li ansify_if(amount, "blue", options.color) .It Sy beg_line Line number where entry for posting begins. .It Sy beg_pos Character position where entry for posting begins. .\".It Sy calculated .It Fn ceiling value Return the next integer of .Ar value toward +infinity. .It Sy cleared Return true if the posting was cleared, false otherwise. .It Sy code Return the transaction code, the string between the parenthesis after the date. .\".It Sy comment .It Fn commodity value Return the commodity of .Ar value or the posting amount when .Ar value was not specified. .\".It Sy cost .\".It Sy count .It Sy date Return the date of the posting. .\".It Sy depth .\".It Sy depth_spacer .\".It Sy display_amount .\".It Sy display_total .It Sy end_line Line number where entry for posting ends. .It Sy end_pos Character position where entry for posting ends. .It Fn floor value Return the next integer of .Ar value toward -infinity. .It Sy filename The name of the .Nm data file from whence the posting came. .It Fn format string Evaluate .Ar string as format just like the .Fl \-format option. .It Fn format_date date format Return the .Ar date as a string using .Ar format . Refer to .Xr strftime 3 for format string details. .It Fn format_datetime datetime format Return the .Ar datetime as a string using .Ar format . Refer to .Xr strftime 3 for format string details. .It Fn get_at seq index Return value at .Ar index from .Ar seq . Used internally to construct different reports. .It Fn has_meta Return true if the posting has metadata named .Ar tag , false otherwise. .It Fn has_tag tag Return true if the posting has metadata named .Ar tag , false otherwise. .It Fn is_seq value Return true if .Ar value is a sequence. Used internally. .It Fn join value Replace all newlines in .Ar value with .Li \en . .It Fn justify value first_width latter_width right_justify colorize Right or left justify the string representing .Ar value . The width of the field in the first line is given by .Ar first_width . For subsequent lines the width is given by .Ar latter_width . If .Ar latter_width is -1, .Ar first_width is used for all lines. If .Ar right_justify is true then the field is right justified within the width of the field. If it is false, then the field is left justified and padded to the full width of the field. If .Ar colorize is true, then ledger will honor color settings. .It Fn market value datetime Return the price of .Ar value at .Ar datetime . Note that .Ar datetime must be surrounded by brackets in order to be parsed correctly, e.g. .Bq 2012/03/23 . .It Fn meta Return the value of metadata named .Ar name . .It Sy note Return the note for the posting. .It Sy now Return the current datetime. .\".It Sy null .It Sy options A variable that allows access to the values of the given command-line options using the long option names, e.g. to see whether .Fl \-daily Pq Fl D was given use .Sy option.daily . .\" .It Sy partial_account .It Sy payee Return the payee of the posting. .It Fn percent value_a value_b Return the percentage of .Ar value_a in relation to .Ar value_b (used as 100%). .It Sy pending Return true if the posting is marked as pending, false otherwise. .It Fn percent value_a value_b Return the percentage of .Ar value_a in relation to .Ar value_b . .\".It Sy post .\" A variable scope .It Fn print value Print .Ar value to stdout. Used internally for debugging. .It Fn quantity value Return the quantity of .Ar value for values that have a per-unit cost. .It Fn quoted expression Surround .Ar expression with double-quotes. .It Sy real .\" Is there a difference between real and actual? Return true if the transaction is real, i.e not a automated or virtual transaction, false otherwise. .\".It Sy rounded .It Fn roundto value n Return .Ar value rounded to .Ar n digits. Does not affect formatting. .It Sy should_bold Return true if expression given to .Fl \-bold-if evaluates to true. Internal use only! .It Fn scrub value Clean .Ar value using various transformations such as round, stripping value annotations, and more. .\".It Sy status .It Fn strip value Strip value annotation from .Ar value . .\".It Sy subcount .It Fn tag name Return the value of tag named .Ar name . .It Fn to_amount value Convert .Ar value to an amount. Internal use only! .It Fn to_balance value Convert .Ar value to a balance. Internal use only! .It Fn to_boolean value Convert .Ar value to a boolean. Internal use only! .It Fn to_date value Convert .Ar value to a date. Internal use only! .It Fn to_datetime value Convert .Ar value to a datetime. Internal use only! .It Fn to_int value Return the integer value for .Ar value . .It Fn to_mask value Convert .Ar value to a mask. Internal use only! .It Fn to_sequence value Convert .Ar value to a sequence. Internal use only! .It Fn to_string value Convert .Ar value to a character string. .It Sy today Return today's date. .It Sy total Return the total of the posting. .It Sy total_expr Return the calculated total of the posting according to the .Fl \-total option. .It Fn trim value Trim leading and trailing whitespace from .Ar value . .It Fn truncated string total_len account_len Truncate .Ar string to .Ar total_len ensuring that each account is at least .Ar account_len long. .\".It Sy uncleared .It Sy virtual Return true if the transaction is virtual, e.g automated, false otherwise. .\".It Sy xact .\" A variable scope .El .\".Sh ENTRIES .\".Sh FORMATS .Sh DEBUG COMMANDS In addition to the regular reporting commands, .Nm also accepts several debug commands: .Bl -tag -width balance .It Ic args Oo Ar report-query Oc Display complete analysis of how .Nm interpreted the given .Ar report-query . Useful if you want to understand how report queries are translated into value expressions. .It Ic eval Oo Ar value-expression Oc Evaluate the given .Ar value-expression and prints the result. For more on value expressions, see the section .Sx EXPRESSIONS . .It Ic format Oo Ar format-string Oc Display an analysis of how .Ar format-string was parsed, and what it would look like applied to a sample transaction. For more on format strings, see the section .Sx FORMATS . .It Ic generate Generate 50 randomly composed yet valid .Nm transactions. .It Ic parse Oo Ar value-expression Oc Parse the given .Ar value-expression and display an analysis of the expression tree and its evaluated value. For more on value expressions, see the section .Sx EXPRESSIONS . .It Ic python Oo Ar file Oc Invoke a Python interpreter to read the given .Ar file . What is special about this is that the .Nm module is builtin, not read from disk, so it doesn't require .Nm to be installed anywhere, or the shared library variants to be built. .It Ic reload Reload all data files for the current session immediately. Can only be used in the .Tn REPL . .It Ic template Oo Ar draft-template Oc Display information about how .Ar draft-template was parsed. See the section on .Sx DRAFTS . .El .Sh ENVIRONMENT Every option to .Nm may be set using an environment variable if the option has a long name. For example setting the environment variable .Ev LEDGER_DATE_FORMAT="%d.%m.%Y' will have the same effect as specifying .Fl \-date-format Ar '%d.%m.%Y' on the command-line. Options on the command-line always take precedence over environment variable settings, however. .Sh FILES .Bl -tag -width -indent .It Pa ~/.ledgerrc Your personal .Nm initializations. .El .Sh SEE ALSO .Xr beancount 1 , .Xr hledger 1 .Sh AUTHORS .An "John Wiegley" .Aq johnw@newartisans.com .\" .Sh BUGS \" Document known, unremedied bugs .\" .Sh HISTORY \" Document history if command behaves in a unique manner ������������������������������������������������������ledger-3.1.1+dfsg1/doc/ledger3.texi�����������������������������������������������������������������0000664�0000000�0000000�00001251547�12660234410�0016760�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������\input texinfo @c -*-texinfo-*- @setfilename ledger3.info @include version.texi @set FIXME:UNDOCUMENTED @sc{undocumented}! Please help by contributing documentation for this feature. @set InternalUseOnly For internal use only. @settitle Ledger: Command-Line Accounting @c Before release, run C-u C-c C-u C-a (texinfo-all-menus-update with @c a prefix arg). This updates the node pointers, which texinfmt.el @c needs. @c | Formating | Indexing | | @c | | @cindex | concept | @c | @command | @findex | Ledger CLI Command (like balance) | @c | @option | @findex | Ledger CLI Option (like --market) | @c | @var | | Ledger CLI option Variable (like -f FILE) | @c | | | Ledger file Syntax | @c | @samp | | Valued example or single char | @c | @file | | File, Buffer | @c | @file | | Program (like ledger, report, acprep) | @c Restructuring manual ideas @c http://beyondgrep.com/documentation/ack-2.04-man.html @c How to make documented ledger examples validate automatically. @c @c The test/DocTests.py script will be run along with the other tests @c when using ctest or acprep check. @c The script parses the texinfo file and looks for three kinds of @c specially marked @smallexamples, then it will run the ledger @c command from the example, and compare the results with the output @c from the documentation. @c @c To specially mark a @smallexample append @c command:UUID, where @c UUID is the first 7 digits from the commands sha1sum, e.g.: @c @c @smallexample @c command:CDE330A @c $ ledger -f sample.dat reg expenses @c @end smallexample @c @c Then DocTests.py will look for corresponding documented output, @c which may appear anywhere in the file, and is marked with @c @smallexample @c output:UUID where UUID is the UUID from the @c corresponding ledger command example, e.g.: @c @c @smallexample @c output:CDE330A @c 04-May-27 Book Store Expenses:Books $20.00 $20.00 @c Expenses:Cards $40.00 $60.00 @c Expenses:Docs $30.00 $90.0 @c @end smallexample @c @c Now where does this data in sample.dat come from? @c DocTests.py is a bit smart about ledger's file argument, since @c it will check if the given filename exists in the test/input/ @c directory. @c @c Sometimes the journal data for an example is specified within @c the documentation itself, in that case the journal example data @c needs to be specially marked as well using @smallexample @c input:UUID, @c again with the UUID being the UUID of the corresponding ledger example @c command. If multiple inputs with the same UUID are found they will be @c concatenated together and given as one set of data to the example command. @c @c @smallexample @c input:35CB2A3 @c 2014/02/09 The Italian Place @c Expenses:Food:Dining $ 36.84 @c Assets:Cash @c @end smallexample @c @c @smallexample @c command:35CB2A3 @c $ ledger -f inline.dat accounts @c @end smallexample @c @c @smallexample @c output:35CB2A3 @c Assets:Cash @c Expenses:Food:Dining @c @end smallexample @c @c To use different example commands with the same input from the documentation @c add with_input:UUID to the example command, where UUID is the UUID of the input, @c e.g.: @c @c @smallexample @c command:94FD2B6,with_input:35CB2A3 @c $ ledger -f inline.dat bal expenses @c @end smallexample @c @c @smallexample @c output:94FD2B6 @c $ 36.84 Expenses:Food:Dining @c @end smallexample @c @c To pass additional input to ledger for certain commands, e.g. convert add @c with_file:filename to the example command and add a file:UUID to an example @c that holds the additional input, where UUID is the UUID of the command, @c e.g.: @c @c @smallexample @c file:download.csv @c 767718,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-8.80,,00001640.04,, @c @end smallexample @c @c @smallexample @c command:94FD2B6,with_file:download.csv @c $ ledger -f sample.dat convert download.csv @c @end smallexample @c @c Additionally DocTests.py will pass --args-only and --columns 80 to ledger @c to ignore any default arguments from the environment or .ledgerrc. @c @c To manually run the tests in this file run: @c $ ./test/DocTests.py -vv --ledger ./ledger --file ./doc/ledger3.texi @copying Copyright @copyright{} 2003–2015, 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: @itemize @item Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @item 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. @item 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. @end itemize 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 @dircategory User Applications @direntry * Ledger3: (ledger3). Command-Line Accounting @end direntry @documentencoding UTF-8 @iftex @finalout @end iftex @titlepage @title Ledger: Command-Line Accounting @subtitle For Version @value{VERSION} of Ledger @author John Wiegley @page @vskip 0pt plus 1filll @insertcopying @end titlepage @contents @ifnottex @node Top, Introduction to Ledger, (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 twinkling in their father's CRT. @end ifnottex @menu * Introduction to Ledger:: * Ledger Tutorial:: * Principles of Accounting with Ledger:: * Keeping a Journal:: * Transactions:: * Building Reports:: * Reporting Commands:: * Command-Line Syntax:: * Budgeting and Forecasting:: * Time Keeping:: * Value Expressions:: * Format Strings:: * Extending with Python:: * Ledger for Developers:: * Major Changes from version 2.6:: * Example Journal File:: * Miscellaneous Notes:: * Concepts Index:: * Commands & Options Index:: @end menu @node Introduction to Ledger, Ledger Tutorial, Top, Top @chapter Introduction to Ledger @menu * Fat-free Accounting:: * Building the program:: * Getting help:: @end menu @node Fat-free Accounting, Building the program, Introduction to Ledger, Introduction to Ledger @section Fat-free Accounting Ledger is an accounting tool with the moxie to exist. It provides no bells or whistles, and returns the user to the days before user interfaces were even a twinkling in their father's CRT. What it does offer is a double-entry accounting journal with all the flexibility and muscle of its modern day cousins, without any of the fat. Think of it as the Bran Muffin of accounting tools. To use it, you need to start keeping a journal. This is the basis of all accounting, and if you haven't started yet, now is the time to learn. The little booklet that comes with your checkbook is a journal, so we'll describe double-entry accounting in terms of that. @c If you use another GUI accounting program like GnuCash, the vast @c majority of its functionality is geared towards helping you keep @c a journal. A checkbook journal records debits (subtractions, or withdrawals) and credits (additions, or deposits) with reference to a single account: the checking account. Where the money comes from, and where it goes to, are described in the payee field, where you write the person or company's name. The ultimate aim of keeping a checkbook journal is to know how much money is available to spend. That's really the aim of all journals. @cindex postings What computers add is the ability to walk through these postings, and tell you things about your spending habits; to let you devise budgets and get control over your spending; to squirrel away money into virtual savings account without having to physically move money around; etc. As you keep your journal, you are recording information about your life and habits, and sometimes that information can start telling you things you aren't aware of. Such is the aim of all good accounting tools. The next step up from a checkbook journal, is a journal that keeps track of all your accounts, not just checking. In such a journal, you record not only who gets paid---in the case of a debit---but where the money came from. In a checkbook journal, it's assumed that all the money comes from your checking account. But in a general journal, you write postings in two lines: the source account and target account. @emph{There must always be a debit from at least one account for every credit made to another account}. This is what is meant by ``double-entry'' accounting: the journal must always balance to zero, with an equal number of debits and credits. For example, let's say you have a checking account and a brokerage account, and you can write checks from both of them. Rather than keep two checkbooks, you decide to use one journal for both. In this general journal you need to record a payment to Pacific Bell for your monthly phone bill, and a transfer (via check) from your brokerage account to your checking account. The Pacific Bell bill is $23.00, let's say, and you want to pay it from your checking account. In the general journal you need to say where the money came from, in addition to where it's going to. These transactions might look like this: @smallexample 9/29 Pacific Bell $23.00 $23.00 Checking $-23.00 0 9/30 Checking $100.00 $100.00 (123) Brokerage $-100.00 0 @end smallexample The posting must balance to $0: $23 went to Pacific Bell, $23 came from Checking. The next entry shows check number 123 written against your brokerage account, transferring money to your checking account. There is nothing left over to be accounted for, since the money has simply moved from one account to another in both cases. This is the basis of double-entry accounting: money never pops in or out of existence; it is always a posting from one account to another. Keeping a general journal is the same as keeping two separate journals: One for Pacific Bell and one for Checking. In that case, each time a payment is written into one, you write a corresponding withdrawal into the other. This makes it easier to write in a ``running balance'', since you don't have to look back at the last time the account was referenced---but it also means having a lot of journal books, if you deal with multiple accounts. @cindex account, meaning of @cindex meaning of account Here is a good place for an aside on the use of the word ``account''. Most private people consider an account to be something that holds money at an institution for them. Ledger uses a more general definition of the word. An account is anywhere money can go. Other finance programs use ``categories'', Ledger uses accounts. So, for example, if you buy some groceries at Trader Joe's, then more groceries at Whole Food Market, you might assign the transactions like this @smallexample @c input:validate 2011/03/15 Trader Joe's Expenses:Groceries $100.00 Assets:Checking 2011/03/15 Whole Food Market Expenses:Groceries $75.00 Assets:Checking @end smallexample In both cases the money goes to the @samp{Groceries} account, even though the payees were different. You can set up your accounts in any way you choose. Enter the beauty of computerized accounting. The purpose of the Ledger program is to make general journal accounting simple, by keeping track of the balances for you. Your only job is to enter the postings. If an individual posting does not balance, Ledger displays an error and indicates the incorrect posting.@footnote{In some special cases, it automatically balances this transaction for you.} In summary, there are two aspects of Ledger use: updating the journal data file, and using the Ledger tool to view the summarized result of your transactions. And just for the sake of example---as a starting point for those who want to dive in head-first---here are the journal transactions from above, formatted as the Ledger program wishes to see them: @smallexample @c input:48DDF26 2004/09/29 Pacific Bell Expenses:Pacific Bell $23.00 Assets:Checking @end smallexample The account balances and registers in this file, if saved as @file{ledger.dat}, could be reported using: @smallexample @c command:48DDF26 $ ledger -f ledger.dat balance @end smallexample @smallexample @c output:48DDF26 $-23.00 Assets:Checking $23.00 Expenses:Pacific Bell -------------------- 0 @end smallexample Or @smallexample @c command:8C7295F,with_input:48DDF26 $ ledger -f ledger.dat register checking @end smallexample @smallexample @c output:8C7295F 04-Sep-29 Pacific Bell Assets:Checking $-23.00 $-23.00 @end smallexample And even: @smallexample @c command:BB32EF2,with_input:48DDF26 $ ledger -f ledger.dat register Bell @end smallexample @smallexample @c output:BB32EF2 04-Sep-29 Pacific Bell Expenses:Pacific Bell $23.00 $23.00 @end smallexample An important difference between Ledger and other finance packages is 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. @node Building the program, Getting help, Fat-free Accounting, Introduction to Ledger @section Building the program Ledger is written in ANSI C++, and should compile on any unix platform. The easiest way to build and install ledger is to use the prepared acprep script, that does a lot of the footwork: @smallexample # to install missing dependencies ./acprep dependencies # building ledger ./acprep update # to run the actual installation make install @end smallexample See the `help` subcommand to `acprep`, which explains some of its many options. You can run `make check` to confirm the result, and `make install` to install. If these intructions do not work for you can check the `INSTALL.md` in the source directory for more up do date build instructions. @node Getting help, , Building the program, Introduction to Ledger @section Getting help @findex help Ledger has a complete online help system based on GNU Info. This manual can be searched directly from the command-line using @code{info ledger}, which will bring up this entire manual in your TTY. Alternatively, the shorter man page can be accessed from the command-line either via @code{man ledger} or @code{ledger --help} If you need help on how to use Ledger, or run into problems, you can join the Ledger mailing list at @url{http://groups.google.com/group/ledger-cli}. You can also find help in the @code{#ledger} channel on the IRC server @code{irc.freenode.net}. @node Ledger Tutorial, Principles of Accounting with Ledger, Introduction to Ledger, Top @chapter Ledger Tutorial @cindex tutorial @menu * Start a Journal File:: * Run a Few Reports:: @end menu @node Start a Journal File, Run a Few Reports, Ledger Tutorial, Ledger Tutorial @section Start a Journal File @cindex journals A journal is a record of your financial transactions and will be central to using Ledger. For now we just want to get a taste of what Ledger can do. An example journal is included with the source code distribution, called @file{drewr3.dat} (@pxref{Example Journal 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 @pxref{Keeping a Journal}. @node Run a Few Reports, , Start a Journal File, Ledger Tutorial @section Run a Few Reports @menu * Balance Report:: * Register Report:: * Cleared Report:: * 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 respect to some special characters. In particular, the ``bash'' shell will interpret @samp{$} signs differently than ledger and they must be escaped to reach the actual program. Another example is ``zsh'', which will interpret @samp{^} differently than ledger expects. In all cases that follow you should take that into account when entering the command-line arguments as given. There are too many variations between shells to give concrete examples for each. @node Balance Report, Register Report, Run a Few Reports, Run a Few Reports @subsection Balance Report @cindex balance report @findex balance To find the balances of all of your accounts, run this command: @smallexample @c command:1071890 $ ledger -f drewr3.dat balance @end smallexample Ledger will generate: @smallexample @c output:1071890 $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business $ -5,200.00 Savings $ -1,000.00 Equity:Opening Balances $ 6,654.00 Expenses $ 5,500.00 Auto $ 20.00 Books $ 300.00 Escrow $ 334.00 Food:Groceries $ 500.00 Interest:Mortgage $ -2,030.00 Income $ -2,000.00 Salary $ -30.00 Sales $ -63.60 Liabilities $ -20.00 MasterCard $ 200.00 Mortgage:Principal $ -243.60 Tithe -------------------- $ -243.60 @end smallexample @noindent Showing you the balance of all accounts. Options and search terms can pare this down to show only the accounts you want. A more useful report is to show only your Assets and Liabilities: @smallexample @c command:5BF4D8E $ ledger -f drewr3.dat balance Assets Liabilities @end smallexample @smallexample @c output:5BF4D8E $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business $ -5,200.00 Savings $ -63.60 Liabilities $ -20.00 MasterCard $ 200.00 Mortgage:Principal $ -243.60 Tithe -------------------- $ -3,867.60 @end smallexample @node Register Report, Cleared Report, Balance Report, Run a Few Reports @subsection Register Report @cindex register report @findex register To show all transactions and a running total: @smallexample @c command:66E3A2C $ ledger -f drewr3.dat register @end smallexample @noindent Ledger will generate: @smallexample @c output:66E3A2C 10-Dec-01 Checking balance Assets:Checking $ 1,000.00 $ 1,000.00 Equit: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 Expense:Food:Groceries $ 37.50 $ 150.00 Expense:Food:Groceries $ 37.50 $ 187.50 Expense:Food:Groceries $ 37.50 $ 225.00 Assets:Checking $ -225.00 0 10-Dec-28 Acme Mortgage Lia:Mortgage:Principal $ 200.00 $ 200.00 Expe:Interest:Mortgage $ 500.00 $ 700.00 Expenses:Escrow $ 300.00 $ 1,000.00 Assets:Checking $ -1,000.00 0 11-Jan-02 Grocery Store Expense:Food:Groceries $ 65.00 $ 65.00 Assets:Checking $ -65.00 0 11-Jan-05 Employer Assets:Checking $ 2,000.00 $ 2,000.00 Income:Salary $ -2,000.00 0 (Liabilities:Tithe) $ -240.00 $ -240.00 11-Jan-14 Bank Assets:Savings $ 300.00 $ 60.00 Assets:Checking $ -300.00 $ -240.00 11-Jan-19 Grocery Store Expense:Food:Groceries $ 44.00 $ -196.00 Assets:Checking $ -44.00 $ -240.00 11-Jan-25 Bank Assets:Checking $ 5,500.00 $ 5,260.00 Assets:Savings $ -5,500.00 $ -240.00 11-Jan-25 Tom's Used Cars Expenses:Auto $ 5,500.00 $ 5,260.00 Assets:Checking $ -5,500.00 $ -240.00 11-Jan-27 Book Store Expenses:Books $ 20.00 $ -220.00 Liabilities:MasterCard $ -20.00 $ -240.00 11-Dec-01 Sale Asse:Checking:Business $ 30.00 $ -210.00 Income:Sales $ -30.00 $ -240.00 (Liabilities:Tithe) $ -3.60 $ -243.60 @end smallexample @noindent To limit this to a more useful subset, simply add the accounts you are interested in seeing transactions for: @cindex accounts, limiting by @cindex limiting by accounts @smallexample @c command:96B0EB3 $ ledger -f drewr3.dat register Groceries @end smallexample @smallexample @c output:96B0EB3 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 Expense:Food:Groceries $ 37.50 $ 150.00 Expense:Food:Groceries $ 37.50 $ 187.50 Expense:Food:Groceries $ 37.50 $ 225.00 11-Jan-02 Grocery Store Expense:Food:Groceries $ 65.00 $ 290.00 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: @smallexample @c command:AECD64E $ ledger -f drewr3.dat balance Groceries @end smallexample @smallexample @c output:AECD64E $ 334.00 Expenses:Food:Groceries @end smallexample @noindent If you would like to find transaction to only a certain payee use @samp{payee} or @samp{@@}: @smallexample @c command:C6BC57E $ ledger -f drewr3.dat register payee "Organic" @end smallexample @smallexample @c output:C6BC57E 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 Expense:Food:Groceries $ 37.50 $ 150.00 Expense:Food:Groceries $ 37.50 $ 187.50 Expense:Food:Groceries $ 37.50 $ 225.00 Assets:Checking $ -225.00 0 @end smallexample @node Cleared Report, Using the Windows Command-Line, Register Report, Run a Few Reports @subsection Cleared Report @cindex cleared report @findex cleared 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 @command{cleared} report shows just that (note that the @command{cleared} report will not format correctly for accounts that contain multiple commodities): @smallexample @c command:B86F6A6 $ ledger -f drewr3.dat cleared @end smallexample @smallexample @c output:B86F6A6 $ -3,804.00 $ 775.00 Assets $ 1,396.00 $ 775.00 10-Dec-20 Checking $ 30.00 0 Business $ -5,200.00 0 Savings $ -1,000.00 $ -1,000.00 10-Dec-01 Equity:Opening Balances $ 6,654.00 $ 225.00 Expenses $ 5,500.00 0 Auto $ 20.00 0 Books $ 300.00 0 Escrow $ 334.00 $ 225.00 10-Dec-20 Food:Groceries $ 500.00 0 Interest:Mortgage $ -2,030.00 0 Income $ -2,000.00 0 Salary $ -30.00 0 Sales $ -63.60 0 Liabilities $ -20.00 0 MasterCard $ 200.00 0 Mortgage:Principal $ -243.60 0 Tithe ---------------- ---------------- --------- $ -243.60 0 @end smallexample @noindent The first column shows the outstanding balance, the second column shows the ``cleared'' balance. @node Using the Windows Command-Line, , Cleared Report, Run a Few Reports @subsection Using the Windows Command-Line @cindex windows cmd.exe @cindex currency symbol display on windows 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 @samp{$}. @node Principles of Accounting with Ledger, Keeping a Journal, Ledger Tutorial, Top @chapter Principles of Accounting with Ledger @menu * Accounting with Ledger:: * Stating where money goes:: * Assets and Liabilities:: * Commodities and Currencies:: * Accounts and Inventories:: * Understanding Equity:: * Dealing with Petty Cash:: * Working with multiple funds and accounts:: @end menu @node Accounting with Ledger, Stating where money goes, Principles of Accounting with Ledger, Principles of Accounting with Ledger @section Accounting with Ledger @cindex double-entry accounting Accounting is simply tracking your money. It can range from nothing, and just waiting for automatic overdraft protection to kick in, or not, to a full-blown double-entry accounting system. Ledger accomplishes the latter. With ledger you can handle your personal finances or your business's. Double-entry accounting scales. @node Stating where money goes, Assets and Liabilities, Accounting with Ledger, Principles of Accounting with Ledger @section Stating where money goes @cindex credits and debits Accountants will talk of ``credits'' and ``debits'', but the meaning is often different from the layman's understanding. To avoid confusion, Ledger uses only subtractions and additions, although the underlying intent is the same as standard accounting principles. Recall that every posting will involve two or more accounts. Money is transferred from one or more accounts to one or more other accounts. To record the posting, an amount is @emph{subtracted} from the source accounts, and @emph{added} to the target accounts. In order to write a Ledger transaction correctly, you must determine where the money comes from and where it goes to. For example, when you are paid a salary, you must add money to your bank account and also subtract it from an income account: @smallexample @c input:validate 9/29 My Employer Assets:Checking $500.00 Income:Salary $-500.00 @end smallexample @cindex income is negative @cindex why is income negative Why is the Income a negative figure? When you look at the balance totals for your ledger, you may be surprised to see that Expenses are a positive figure, and Income is a negative figure. It may take some getting used to, but to properly use a general ledger you must think in terms of how money moves. Rather than Ledger ``fixing'' the minus signs, let's understand why they are there. When you earn money, the money has to come from somewhere. Let's call that somewhere ``society''. In order for society to give you an income, you must take money away (withdraw) from society in order to put it into (make a payment to) your bank. When you then spend that money, it leaves your bank account (a withdrawal) and goes back to society (a payment). This is why Income will appear negative---it reflects the money you have drawn from society---and why Expenses will be positive---it is the amount you've given back. These additions and subtractions will always cancel each other out in the end, because you don't have the ability to create new money: it must always come from somewhere, and in the end must always leave. This is the beginning of economy, after which the explanation gets terribly difficult. Based on that explanation, here's another way to look at your balance report: every negative figure means that that account or person or place has less money now than when you started your ledger; and every positive figure means that that account or person or place has more money now than when you started your ledger. Make sense? @node Assets and Liabilities, Commodities and Currencies, Stating where money goes, Principles of Accounting with Ledger @section Assets and Liabilities @cindex assets and liabilities @cindex debts are liabilities Assets are money that you have, and Liabilities are money that you owe. ``Liabilities'' is just a more inclusive name for Debts. An Asset is typically increased by transferring money from an Income account, such as when you get paid. Here is a typical transaction: @smallexample @c input:6B43DD4 2004/09/29 My Employer Assets:Checking $500.00 Income:Salary @end smallexample Money, here, comes from an Income account belonging to @samp{My Employer}, and is transferred to your checking account. The money is now yours, which makes it an Asset. Liabilities track money owed to others. This can happen when you borrow money to buy something, or if you owe someone money. Here is an example of increasing a MasterCard liability by spending money with it: @smallexample @c input:6B43DD4 2004/09/30 Restaurant Expenses:Dining $25.00 Liabilities:MasterCard @end smallexample The Dining account balance now shows $25 spent on Dining, and a corresponding $25 owed on the MasterCard---and therefore shown as $-25.00. The MasterCard liability shows up as negative because it offsets the value of your assets. The combined total of your Assets and Liabilities is your net worth. So to see your current net worth, use this command: @smallexample @c command:6B43DD4 $ ledger balance ^assets ^liabilities @end smallexample @smallexample @c output:6B43DD4 $500.00 Assets:Checking $-25.00 Liabilities:MasterCard -------------------- $475.00 @end smallexample In a similar vein, your Income accounts show up negative, because they transfer money @emph{from} an account in order to increase your assets. Your Expenses show up positive because that is where the money went to. The combined total of Income and Expenses is your cash flow. A positive cash flow means you are spending more than you make, since income is always a negative figure. To see your current cash flow, use this command: @smallexample @c command:DB128F3,with_input:6B43DD4 $ ledger balance ^income ^expenses @end smallexample @smallexample @c output:DB128F3 $25.00 Expenses:Dining $-500.00 Income:Salary -------------------- $-475.00 @end smallexample Another common question to ask of your expenses is: How much do I spend each month on X? Ledger provides a simple way of displaying monthly totals for any account. Here is an example that summarizes your monthly automobile expenses: @smallexample @c command:DB524E4 $ ledger -M register -f drewr3.dat expenses:auto @end smallexample @smallexample @c output:DB524E4 11-Jan-01 - 11-Jan-31 Expenses:Auto $ 5,500.00 $ 5,500.00 @end smallexample This assumes, of course, that you use account names like @samp{Expenses:Auto:Gas} and @samp{Expenses:Auto:Repair}. @menu * Tracking reimbursable expenses:: @end menu @node Tracking reimbursable expenses, , Assets and Liabilities, Assets and Liabilities @subsection Tracking reimbursable expenses @cindex reimbursable expense tracking Sometimes you will want to spend money on behalf of someone else, which will eventually get repaid. Since the money is still @emph{yours}, it is really an asset. And since the expenditure was for someone else, you don't want it contaminating your Expenses reports. You will need to keep an account for tracking reimbursements. This is fairly easy to do in ledger. When spending the money, spend it @emph{to} your Assets:Reimbursements, using a different account for each person or business that you spend money for. For example: @smallexample @c input:validate 2004/09/29 Circuit City Assets:Reimbursements:Company XYZ $100.00 Liabilities:MasterCard @end smallexample This shows $100.00 spent on a MasterCard at Circuit City, with the expense was made on behalf of Company XYZ. Later, when Company XYZ pays the amount back, the money will transfer from that reimbursement account back to a regular asset account: @smallexample @c input:validate 2004/09/29 Company XYZ Assets:Checking $100.00 Assets:Reimbursements:Company XYZ @end smallexample This deposits the money owed from Company XYZ into a checking account, presumably because they paid the amount back with a check. But what to do if you run your own business, and you want to keep track of expenses made on your own behalf, while still tracking everything in a single ledger file? This is more complex, because you need to track two separate things: 1) The fact that the money should be reimbursed to you, and 2) What the expense account was, so that you can later determine where your company is spending its money. This kind of posting is best handled with mirrored postings in two different files, one for your personal accounts, and one for your company accounts. But keeping them in one file involves the same kinds of postings, so those are what is shown here. First, the personal transaction, which shows the need for reimbursement: @smallexample @c input:validate 2004/09/29 Circuit City Assets:Reimbursements:Company XYZ $100.00 Liabilities:MasterCard @end smallexample This is the same as above, except that you own Company XYZ, and are keeping track of its expenses in the same ledger file. This transaction should be immediately followed by an equivalent transaction, which shows the kind of expense, and also notes the fact that $100.00 is now payable to you: @smallexample @c input:validate 2004/09/29 Circuit City Company XYZ:Expenses:Computer:Software $100.00 Company XYZ:Accounts Payable:Your Name @end smallexample This second transaction shows that Company XYZ has just spent $100.00 on software, and that this $100.00 came from Your Name, which must be paid back. These two transactions can also be merged, to make things a little clearer. Note that all amounts must be specified now: @smallexample @c input:validate 2004/09/29 Circuit City Assets:Reimbursements:Company XYZ $100.00 Liabilities:MasterCard $-100.00 Company XYZ:Expenses:Computer:Software $100.00 Company XYZ:Accounts Payable:Your Name $-100.00 @end smallexample To ``pay back'' the reimbursement, just reverse the order of everything, except this time drawing the money from a company asset, paying it to accounts payable, and then drawing it again from the reimbursement account, and paying it to your personal asset account. It's easier shown than said: @smallexample @c input:validate 2004/10/15 Company XYZ Assets:Checking $100.00 Assets:Reimbursements:Company XYZ $-100.00 Company XYZ:Accounts Payable:Your Name $100.00 Company XYZ:Assets:Checking $-100.00 @end smallexample And now the reimbursements account is paid off, accounts payable is paid off, and $100.00 has been effectively transferred from the company's checking account to your personal checking account. The money simply ``waited''---in both @samp{Assets:Reimbursements:Company XYZ}, and @samp{Company XYZ:Accounts Payable:Your Name}---until such time as it could be paid off. The value of tracking expenses from both sides like that is that you do not contaminate your personal expense report with expenses made on behalf of others, while at the same time making it possible to generate accurate reports of your company's expenditures. It is more verbose than just paying for things with your personal assets, but it gives you a very accurate information trail. The advantage to keep these doubled transactions together is that they always stay in sync. The advantage to keeping them apart is that it clarifies the transfer's point of view. To keep the postings in separate files, just separate the two transactions that were joined above. For example, for both the expense and the pay-back shown above, the following four transactions would be created. Two in your personal ledger file: @smallexample @c input:990E0D1 2004/09/29 Circuit City Assets:Reimbursements:Company XYZ $100.00 Liabilities:MasterCard $-100.00 2004/10/15 Company XYZ Assets:Checking $100.00 Assets:Reimbursements:Company XYZ $-100.00 @end smallexample And two in your company ledger file: @smallexample @c input:990E0D1 apply account Company XYZ 2004/09/29 Circuit City Expenses:Computer:Software $100.00 Accounts Payable:Your Name $-100.00 2004/10/15 Company XYZ Accounts Payable:Your Name $100.00 Assets:Checking $-100.00 end apply account @end smallexample (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). After creating these transactions, you will always know that $100.00 was spent using your MasterCard on behalf of Company XYZ, and that Company XYZ spent the money on computer software and paid it back about two weeks later. @smallexample @c command:990E0D1 $ ledger balance --no-total @end smallexample @smallexample @c output:990E0D1 $100.00 Assets:Checking 0 Company XYZ $-100.00 Assets:Checking $100.00 Expenses:Computer:Software $-100.00 Liabilities:MasterCard @end smallexample @node Commodities and Currencies, Accounts and Inventories, Assets and Liabilities, Principles of Accounting with Ledger @section Commodities and Currencies Ledger makes no assumptions about the commodities you use; it only requires that you specify a commodity. The commodity may be any non-numeric string that does not contain a period, comma, forward slash or at-sign. It may appear before or after the amount, although it is assumed that symbols appearing before the amount refer to currencies, while non-joined symbols appearing after the amount refer to commodities. Here are some valid currency and commodity specifiers: @smallexample $20.00 ; currency: twenty US dollars 40 AAPL ; commodity: 40 shares of Apple stock 60 DM ; currency: 60 Deutsch Mark £50 ; currency: 50 British pounds 50 EUR ; currency: 50 Euros (or use appropriate symbol) @end smallexample Ledger will examine the first use of any commodity to determine how that commodity should be printed on reports. It pays attention to whether the name of commodity was separated from the amount, whether it came before or after, the precision used in specifying the amount, whether thousand marks were used, etc. This is done so that printing the commodity looks the same as the way you use it. An account may contain multiple commodities, in which case it will have separate totals for each. For example, if your brokerage account contains both cash, gold, and several stock quantities, the balance might look like: @smallexample $200.00 100.00 AU AAPL 40 BORL 100 FEQTX 50 Assets:Brokerage @end smallexample This balance report shows how much of each commodity is in your brokerage account. Sometimes, you will want to know the current street value of your balance, and not the commodity totals. For this to happen, you must specify what the current price is for each commodity. The price can be any commodity, in which case the balance will be computed in terms of that commodity. The usual way to specify prices is with a price history file, which might look like this: @smallexample @c input:validate P 2004/06/21 02:18:01 FEQTX $22.49 P 2004/06/21 02:18:01 BORL $6.20 P 2004/06/21 02:18:02 AAPL $32.91 P 2004/06/21 02:18:02 AU $400.00 @end smallexample @findex --price-db @var{FILE} @findex --market Specify the price history to use with the @option{--price-db @var{FILE}} option, with the @option{--market (-V)} option to report in terms of current market value: @smallexample $ ledger --price-db prices.db -V balance brokerage @end smallexample The balance for your brokerage account will be reported in US dollars, since the prices database uses that currency. @smallexample $40880.00 Assets:Brokerage @end smallexample You can convert from any commodity to any other commodity. Let's say you had $5000 in your checking account, and for whatever reason you wanted to know many ounces of gold that would buy, in terms of the current price of gold: @smallexample $ ledger -T "@{1 AU@}*(O/P@{1 AU@})" balance checking @end smallexample Although the total expression appears complex, it is simply saying that the reported total should be in multiples of AU units, where the quantity is the account total divided by the price of one AU. Without the initial multiplication, the reported total would still use the dollars commodity, since multiplying or dividing amounts always keeps the left value's commodity. The result of this command might be: @smallexample 14.01 AU Assets:Checking @end smallexample @menu * Commodity price histories:: * Commodity equivalences:: @end menu @node Commodity price histories, Commodity equivalences, Commodities and Currencies, Commodities and Currencies @subsection Commodity price histories Whenever a commodity is purchased using a different commodity (such as a share of common stock using dollars), it establishes a price for that commodity on that day. It is also possible, by recording price details in a ledger file, to specify other prices for commodities at any given time. Such price transactions might look like those below: @smallexample @c input:validate P 2004/06/21 02:17:58 TWCUX $27.76 P 2004/06/21 02:17:59 AGTHX $25.41 P 2004/06/21 02:18:00 OPTFX $39.31 P 2004/06/21 02:18:01 FEQTX $22.49 P 2004/06/21 02:18:02 AAPL $32.91 @end smallexample By default, ledger will not consider commodity prices when generating its various reports. It will always report balances in terms of the commodity total, rather than the current value of those commodities. To enable pricing reports, use one of the commodity reporting options. @node Commodity equivalences, , Commodity price histories, Commodities and Currencies @subsection Commodity equivalences Sometimes a commodity has several forms which are all equivalent. An example of this is time. Whether tracked in terms of minutes, hours or days, it should be possible to convert between the various forms. Doing this requires the use of commodity equivalences. For example, you might have the following two postings, one which transfers an hour of time into a @samp{Billable} account, and another which decreases the same account by ten minutes. The resulting report will indicate that fifty minutes remain: @smallexample @c input:DF3FEBE 2005/10/01 Work done for company Billable:Client 1h Project:XYZ 2005/10/02 Return ten minutes to the project Project:XYZ 10m Billable:Client @end smallexample Reporting the balance for this ledger file produces: @smallexample @c command:DF3FEBE $ ledger --no-total balance Billable Project @end smallexample @smallexample @c output:DF3FEBE 50.0m Billable:Client -50.0m Project:XYZ @end smallexample @findex C This example works because ledger already knows how to handle seconds, minutes and hours, as part of its time tracking support. Defining other equivalences is simple. The following is an example that creates data equivalences, helpful for tracking bytes, kilobytes, megabytes, and more: @smallexample @c input:validate C 1.00 Kb = 1024 b C 1.00 Mb = 1024 Kb C 1.00 Gb = 1024 Mb C 1.00 Tb = 1024 Gb @end smallexample Each of these definitions correlates a commodity (such as @samp{Kb}) and a default precision, with a certain quantity of another commodity. In the above example, kilobytes are reported with two decimal places of precision and each kilobyte is equal to 1024 bytes. Equivalence chains can be as long as desired. Whenever a commodity would report as a decimal amount (less than @samp{1.00}), the next smallest commodity is used. If a commodity could be reported in terms of a higher commodity without resulting to a partial fraction, then the larger commodity is used. @node Accounts and Inventories, Understanding Equity, Commodities and Currencies, Principles of Accounting with Ledger @section Accounts and Inventories Since Ledger's accounts and commodity system is so flexible, you can have accounts that don't really exist, and use commodities that no one else recognizes. For example, let's say you are buying and selling various items in EverQuest, and want to keep track of them using a ledger. Just add items of whatever quantity you wish into your EverQuest account: @smallexample @c input:48F4E47 9/29 Get some stuff at the Inn Places:Black's Tavern -3 Apples Places:Black's Tavern -5 Steaks EverQuest:Inventory @end smallexample Now your EverQuest:Inventory has 3 apples and 5 steaks in it. The amounts are negative, because you are taking @emph{from} Black's Tavern in order to add to your Inventory account. Note that you don't have to use @samp{Places:Black's Tavern} as the source account. You could use @samp{EverQuest:System} to represent the fact that you acquired them online. The only purpose for choosing one kind of source account over another is to generate more informative reports later on. The more you know, the better the analysis you can perform. If you later sell some of these items to another player, the transaction would look like: @smallexample @c input:48F4E47 10/2 Sturm Brightblade EverQuest:Inventory -2 Steaks EverQuest:Inventory 15 Gold @end smallexample Now you've turned 2 steaks into 15 gold, courtesy of your customer, Sturm Brightblade. @smallexample @c command:48F4E47 $ ledger balance EverQuest @end smallexample @smallexample @c output:48F4E47 3 Apples 15 Gold 3 Steaks EverQuest:Inventory @end smallexample @node Understanding Equity, Dealing with Petty Cash, Accounts and Inventories, Principles of Accounting with Ledger @section Understanding Equity The most confusing transaction in any ledger will be your equity account---because starting balances can't come out of nowhere. When you first start your ledger, you will likely already have money in some of your accounts. Let's say there's $100 in your checking account; then add a transaction to your ledger to reflect this amount. Where will the money come from? The answer: your equity. @smallexample @c input:validate 10/2 Opening Balance Assets:Checking $100.00 Equity:Opening Balances @end smallexample But what is equity? You may have heard of equity when people talked about house mortgages, as ``the part of the house that you own''. Basically, equity is like the value of something. If you own a car worth $5000, then you have $5000 in equity in that car. In order to turn that car (a commodity) into a cash flow, or a credit to your bank account, you will have to debit the equity by selling it. When you start a ledger, you are probably already worth something. Your net worth is your current equity. By transferring the money in the ledger from your equity to your bank accounts, you are crediting the ledger account based on your prior equity. That is why, when you look at the balance report, you will see a large negative number for Equity that never changes: Because that is what you were worth (what you debited from yourself in order to start the ledger) before the money started moving around. If the total positive value of your assets is greater than the absolute value of your starting equity, it means you are making money. Clear as mud? Keep thinking about it. Until you figure it out, put @code{not 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 with Ledger @section Dealing with Petty Cash Something that stops many people from keeping a ledger at all is the insanity of tracking small cash expenses. They rarely generate a receipt, and there are often a lot of small postings, rather than a few large ones, as with checks. One solution is: don't bother. Move your spending to a debit card, but in general ignore cash. Once you withdraw it from the ATM, mark it as already spent to an @samp{Expenses:Cash} category: @smallexample @c input:validate 2004/03/15 ATM Expenses:Cash $100.00 Assets:Checking @end smallexample If at some point you make a large cash expense that you want to track, just @emph{move} the amount of the expense from @samp{Expenses:Cash} into the target account: @smallexample @c input:validate 2004/03/20 Somebody Expenses:Food $65.00 Expenses:Cash @end smallexample This way, you can still track large cash expenses, while ignoring all of the smaller ones. @node Working with multiple funds and accounts, , Dealing with Petty Cash, Principles of Accounting with Ledger @section Working with multiple funds and accounts There are situations when the accounts you're tracking are different between your clients and the financial institutions where money is kept. An example of this is working as the treasurer for a religious institution. From the secular point of view, you might be working with three different accounts: @itemize @item Checking @item Savings @item Credit Card @end itemize From a religious point of view, the community expects to divide its resources into multiple ``funds'', from which it makes purchases or reserves resources for later: @itemize @item School fund @item Building fund @item Community fund @end itemize The problem with this kind of setup is that, when you spend money, it comes from two or more places at once: the account and the fund. And yet, the correlation of amounts between funds and accounts is rarely one-to-one. What if the school fund has @samp{$500.00}, but @samp{$400.00} of that comes from Checking, and @samp{$100.00} from Savings? Traditional finance packages require that the money reside in only one place. But there are really two ``views'' of the data: from the account point of view and from the fund point of view---yet both sets should reflect the same overall expenses and cash flow. It's simply where the money resides that differs. This situation can be handled one of two ways. The first is using virtual postings to represent the fact that money is moving to and from two kind of accounts at the same time: @smallexample @c input:396F24E 2004/03/20 Contributions Assets:Checking $500.00 Income:Donations 2004/03/25 Distribution of donations [Funds:School] $300.00 [Funds:Building] $200.00 [Assets:Checking] $-500.00 @end smallexample The use of square brackets in the second transaction ensures that the virtual postings balance to zero. Now money can be spent directly from a fund at the same time as money is drawn from a physical account: @smallexample @c input:396F24E 2004/03/25 Payment for books (paid from Checking) Expenses:Books $100.00 Assets:Checking $-100.00 (Funds:School) $-100.00 @end smallexample When reports are generated, by default they'll appear in terms of the funds. In this case, you will likely want to mask out your @samp{Assets} account, because otherwise the balance won't make much sense: @smallexample @c command:396F24E $ ledger --no-total bal not ^Assets @end smallexample @smallexample @c output:396F24E $100.00 Expenses:Books $400.00 Funds $200.00 Building $200.00 School $-500.00 Income:Donations @end smallexample @findex --real If the @option{--real} option is used, the report will be in terms of the real accounts: @smallexample @c command:2F1CB75,with_input:396F24E $ ledger --real --no-total bal @end smallexample @smallexample @c output:2F1CB75 $400.00 Assets:Checking $100.00 Expenses:Books $-500.00 Income:Donations @end smallexample If more asset accounts are needed as the source of a posting, just list them as you would normally, for example: @smallexample @c input:validate 2004/03/25 Payment for books (paid from Checking) Expenses:Books $100.00 Assets:Checking $-50.00 Liabilities:Credit Card $-50.00 (Funds:School) $-100.00 @end smallexample The second way of tracking funds is to use 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: @smallexample @c input:AD068BA 2004/03/25 (Funds:School) Donations Assets:Checking $100.00 Income:Donations 2004/03/25 (Funds:Building) Donations Assets:Checking $20.00 Income:Donations 2004/04/25 (Funds:School) Payment for books Expenses:Books $50.00 Assets:Checking @end smallexample Note how the accounts now relate only to the real accounts, and any balance or register reports will reflect this. That the transactions relate to a particular fund is kept only in the code. @findex --payee=code @findex --by-payee How does this become a fund report? By using the @option{--payee=code} 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 (-P)} 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: @smallexample @c command:AD068BA $ ledger --payee=code -P reg ^Assets @end smallexample @smallexample @c output:AD068BA 04-Mar-25 Funds:Building Assets:Checking $20.00 $20.00 04-Mar-25 Funds:School Assets:Checking $50.00 $70.00 @end smallexample Or to see a particular fund's expenses, the @samp{School} fund in this case: @smallexample @c command:E30B2FC,with_input:AD068BA $ ledger --payee=code -P reg ^Expenses and code School @end smallexample @smallexample @c output:E30B2FC 04-Apr-25 Funds:School Expenses:Books $50.00 $50.00 @end smallexample Both approaches yield different kinds of flexibility, depending on how you prefer to think of your funds: as virtual accounts, or as tags associated with particular transactions. Your own tastes will decide which is best for your situation. @node Keeping a Journal, Transactions, Principles of Accounting with Ledger, Top @chapter Keeping a Journal The most important part of accounting is keeping a good journal. If you have a good journal, tools can be written to work whatever mathematical tricks you need to better understand your spending patterns. Without a good journal, no tool, however smart, can help you. The Ledger program aims at making journal transactions as simple as possible. Since it is a command-line tool, it does not provide a user interface for keeping a journal. If you require an user interface to maintain journal transactions GnuCash is a good alternative. If you are not using GnuCash, but a text editor to maintain your journal, read on. Ledger has been designed to make data transactions as simple as possible, by keeping the journal format easy, and also by automagically determining as much information as possible based on the nature of your transactions. For example, you do not need to tell Ledger about the accounts you use. Any time Ledger sees a posting involving an account it knows nothing about, it will create it@footnote{This also means if you misspell an account it will end up getting counted separately from what you intended. The provided Emacs major mode provides for automatically filling in account names.}. If you use a commodity that is new to Ledger, it will create that commodity, and determine its display characteristics (placement of the symbol before or after the amount, display precision, etc.) based on how you used the commodity in the posting. @menu * The Most Basic Entry:: * Starting up:: * Structuring your Accounts:: * Commenting on your Journal:: * Currency and Commodities:: * Keeping it Consistent:: * Journal Format:: * Converting from other formats:: * Archiving Previous Years:: @end menu @node The Most Basic Entry, Starting up, Keeping a Journal, Keeping a Journal @section The Most Basic Entry Here is the Pacific Bell example from above, given as a Ledger posting, with the addition of a check number: @smallexample @c input:validate 9/29 (1023) Pacific Bell Expenses:Utilities:Phone $23.00 Assets:Checking $-23.00 @end smallexample As you can see, it is very similar to what would be written on paper, minus the computed balance totals, and adding in account names that work better with Ledger's scheme of things. In fact, since Ledger is smart about many things, you don't need to specify the balanced amount, if it is the same as the first line: @smallexample @c input:validate 9/29 (1023) Pacific Bell Expenses:Utilities:Phone $23.00 Assets:Checking @end smallexample For this transaction, Ledger will figure out that $-23.00 must come from @samp{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 (@pxref{Structuring your Accounts}). @cindex spaces in postings @cindex posting format details @strong{The format is very flexible and it isn't necessary that you indent and space out things exactly as shown. The only requirements are that the start of the transaction (the date typically) is at the beginning of the first line of the transaction, and the accounts are indented by at least one space. If you omit the leading spaces in the account lines Ledger will generate an error. There must be at least two spaces, or a tab, between the amount and the account. If you do not have adequate separation between the amount and the account Ledger will give an error and stop calculating.} @node Starting up, Structuring your Accounts, The Most Basic Entry, Keeping a Journal @section Starting up @cindex initial equity @cindex beginning ledger @cindex opening balance Unless you have recently arrived from another planet, you already have a financial state. You need to capture that financial state so that Ledger has a starting point. At some convenient point in time you knew the balances and outstanding obligation of every financial account you have. Those amounts form the basis of the opening entry for ledger. For example if you chose the beginning of 2011 as the date to start tracking finances with ledger, your opening balance entry could look like this: @smallexample @c input:validate 2011/01/01 * Opening Balance 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 Liabilities:Mortgage $-175634.88 Liabilities:Car Loan $-3494.26 Liabilities:Visa -$1762.44 Equity:Opening Balances @end smallexample There is nothing special about the name ``Opening Balances'' as the payee of the account name, anything convenient that you understand will work. @node Structuring your Accounts, Commenting on your Journal, Starting up, Keeping a Journal @section Structuring your Accounts @cindex accounts, naming @cindex naming accounts There really are no requirements for how you do this, but to preserve your sanity we suggest some very basic structure to your accounting system. At the highest level you have five sorts of accounts: @enumerate @item Expenses: where money goes, @item Assets: where money sits, @item Income: where money comes from, @item Liabilities: money you owe, @item Equity: the real value of your property. @end enumerate Starting the structure off this way will make it simpler for you to get answers to the questions you really need to ask about your finances. Beneath these top level accounts you can have any level of detail you desire. For example, if you want to keep specific track of how much you spend on burgers and fries, you could have the following: @smallexample @c input:validate Expenses:Food:Hamburgers and Fries @end smallexample @node Commenting on your Journal, Currency and Commodities, Structuring your Accounts, Keeping a Journal @section Commenting on your Journal @cindex comments, characters @cindex block comments @cindex comments, block Comments are generally started using a @samp{;}. However, in order to increase compatibility with other text manipulation programs and methods, four additional comment characters are valid if used at the beginning of a line: @samp{#}, @samp{|}, and @samp{*} and @samp{%}. Block comments can be made by use @code{comment} ... @code{end comment}. @smallexample @c input:validate ; This is a single line comment, # and this, % and this, | and this, * and this. comment This is a block comment with multiple lines end comment @end smallexample There are several forms of comments within a transaction, for example: @smallexample @c input:validate ; this is a global comment that is not applied to a specific transaction ; it can start with any of the five characters but is not included in the ; output from 'print' or 'output' 2011/12/11 Something Sweet ; German Chocolate Cake ; :Broke Diet: Expenses:Food $10.00 ; Friends: The gang Assets:Credit Union:Checking @end smallexample @noindent The first comment is global and Ledger will not attach it to any specific transactions. The comments within the transaction must all start with @samp{;} and are preserved as part of the transaction. The @samp{:} indicates meta-data and tags (@pxref{Metadata}). @node Currency and Commodities, Keeping it Consistent, Commenting on your Journal, Keeping a Journal @section Currency and Commodities @cindex currency @cindex commodity Ledger is agnostic when it comes to how you value your accounts. Dollars, Euros, Pounds, Francs, Shares etc. are all just ``commodities''. Holdings in stocks, bonds, mutual funds and other financial instruments can be labeled using whatever is convenient for you (stock ticker symbols are suggested for publicly traded assets).@footnote{You can track @emph{anything}, even time or distance traveled. As long as it cannot be created or destroyed inside your accounting system.} For the rest of this manual, we will only use the word ``commodities'' when referring to the units on a transaction value. This is fundamentally different than many common accounting packages, which assume the same currency throughout all of your accounts. This means if you typically operate in Euros, but travel to the US and have some expenses, you would have to do the currency conversion @emph{before} you made the entry into your financial system. With ledger this is not required. In the same journal you can have entries in any or all commodities you actually hold. You can use the reporting capabilities to convert all commodities to a single commodity for reporting purposes without ever changing the underlying entry. For example, the following entries reflect transactions made for a business trip to Europe from the US: @smallexample @c input:82150D9 2011/09/23 Cash in Munich Assets:Cash €50.00 Assets:Checking $-66.00 2011/09/24 Dinner in Munich Expenses:Business:Travel €35.00 Assets:Cash @end smallexample This says that $66.00 came out of checking and turned into 50 Euros. The implied exchange rate was $1.32. Then 35.00 Euros were spent on Dinner in Munich. Running a ledger balance report shows: @smallexample @c command:82150D9 $ ledger -f example.dat bal @end smallexample @smallexample @c output:82150D9 $-66.00 €15.00 Assets €15.00 Cash $-66.00 Checking €35.00 Expenses:Business:Travel -------------------- $-66.00 €50.00 @end smallexample The top two lines show my current assets as $-66.00 in checking (in this very short example I didn't establish opening an opening balance for the checking account) and €15.00. After spending on dinner I have €15.00 in my wallet. The bottom line balances to zero, but is shown in two lines since we haven't told ledger to convert commodities. @menu * Naming Commodities:: * Buying and Selling Stock:: * Fixing Lot Prices:: * Complete control over commodity pricing:: @end menu @node Naming Commodities, Buying and Selling Stock, Currency and Commodities, Currency and Commodities @subsection Naming Commodities Commodity names can have any character, including white-space. However, if you include white-space or numeric characters, the commodity name must be enclosed in double quotes @samp{"}: @smallexample @c input:validate 1999/06/09 ! Achat Actif:SG PEE STK 49.957 "Arcancia Équilibre 454" Actif:SG PEE STK $-234.90 2000/12/08 ! Achat Actif:SG PEE STK 215.796 "Arcancia Équilibre 455" Actif:SG PEE STK $-10742.54 @end smallexample @node Buying and Selling Stock, Fixing Lot Prices, Naming Commodities, Currency and Commodities @subsection Buying and Selling Stock @cindex buying stock Buying stock is a typical example that many will use that involves multiple commodities in the same transaction. The type of the share (AAPL for Apple Inc.) and the share purchase price in the currency unit you made the purchase in ($ for AAPL). Yes, the typical convention is as follows: @smallexample @c input:validate 2004/05/01 Stock purchase Assets:Broker 50 AAPL @@ $30.00 Expenses:Broker:Commissions $19.95 Assets:Broker $-1,519.95 @end smallexample This assumes you have a brokerage account that is capable of managing both liquid and commodity assets. Now, on the day of the sale: @smallexample @c input:validate 2005/08/01 Stock sale Assets:Broker -50 AAPL @{$30.00@} @@ $50.00 Expenses:Broker:Commissions $19.95 Income:Capital Gains $-1,000.00 Assets:Broker $2,480.05 @end smallexample @noindent You can, of course, elide the amount of the last posting. It is there for clarity's sake. The @samp{@{$30.00@}} is a lot price. You can also use a lot date, @samp{[2004/05/01]}, or both, in case you have several lots of the same price/date and your taxation model is based on longest-held-first. @node Fixing Lot Prices, Complete control over commodity pricing, Buying and Selling Stock, Currency and Commodities @subsection Fixing Lot Prices @cindex fixing lot prices @cindex consumable commodity pricing Commodities that you keep in order to sell at a later time have a variable value that fluctuates with the market prices. Commodities that you consume should not fluctuate in value, but stay at the lot price they were purchased at. As an extension of ``lot pricing'', you can fix the per-unit price of a commodity. For example, say you buy 10 gallons of gas at $1.20. In future ``value'' reports, you don't want these gallons reported in terms of today's price, but rather the price when you bought it. At the same time, you also want other kinds of commodities---like stocks--- reported in terms of today's price. This is supported as follows: @smallexample @c input:validate 2009/01/01 Shell Expenses:Gasoline 11 GAL @{=$2.299@} Assets:Checking @end smallexample This transaction actually introduces a new commodity, @samp{GAL @{=$2.29@}}, 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 sign compared to the transaction above): @smallexample @c input:validate 2009/01/01 Shell Expenses:Gasoline 11 GAL @{$2.299@} Assets:Checking 2009/01/01 Shell Expenses:Gasoline 11 GAL @@ $2.299 Assets:Checking @end smallexample There is no difference in meaning between these two forms. Why do both exist, you ask? To support things like this: @smallexample @c input:validate 2009/01/01 Shell Expenses:Gasoline 11 GAL @{=$2.299@} @@ $2.30 Assets:Checking @end smallexample This transaction says that you bought 11 gallons priced at $2.299 per gallon at a @emph{cost to you} of $2.30 per gallon. Ledger auto-generates a balance posting in this case to Equity:Capital Losses to reflect the 1.1 cent difference, which is then balanced by Assets:Checking because its amount is null. @node Complete control over commodity pricing, , Fixing Lot Prices, Currency and Commodities @subsection Complete control over commodity pricing @findex --market @findex --exchange @var{COMMODITY} Ledger allows you to have very detailed control over how your commodities are valued. You can fine tune the results given using the @option{--market} or @option{--exchange @var{COMMODITY}} 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 (effect 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 @samp{@{=$20@}}) still plays a role in this scheme. As far as valuation goes, it's shorthand for writing @samp{((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: @samp{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 @option{--market} was used instead of @option{--exchange @var{COMMODITY}}. @end table The valuation function should return an amount. If you've written your function in Python, you can return something like @samp{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 @c input:validate 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 @c input:validate 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 @c input:validate 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 @c input:validate 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 @c input:validate account Expenses:Food5 value myfunc_five @end smallexample The metadata field @samp{Value}, if found, overrides the valuation function on a transaction-wide or per-posting basis. @smallexample @c input:validate = @@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 @samp{(( ))} commodity annotation. @smallexample @c input:814A366 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 @c command:814A366 $ ledger reg -V food @end smallexample @smallexample @c output:814A366 12-Mar-02 KFC Expenses:Food2 2 EUR 2 EUR 12-Mar-03 KFC Expenses:Food3 3 EUR 5 EUR 12-Mar-04 KFC Expenses:Food4 4 EUR 9 EUR 12-Mar-05 KFC Expenses:Food5 $1 $1 9 EUR 12-Mar-06 KFC Expenses:Food6 $1 $2 9 EUR 12-Mar-07 KFC Expenses:Food7 1 CAD $2 1 CAD 9 EUR 12-Mar-08 XACT Expenses:Food8 $1 $3 1 CAD 9 EUR @end smallexample @node Keeping it Consistent, Journal Format, Currency and Commodities, Keeping a Journal @section Keeping it Consistent @findex --strict @findex accounts Sometimes Ledger's flexibility can lead to difficulties. Using a freeform text editor to enter transactions makes it easy to keep the data, but also easy to enter accounts or payees inconsistently or with spelling errors. In order to combat inconsistency you can define allowable accounts and payees. For simplicity, create a separate text file and define accounts and payees like @smallexample @c input:validate account Expenses account Expenses:Utilities @end smallexample Using the @option{--strict} option will cause Ledger to complain if any accounts are not previously defined: @smallexample $ ledger bal --strict Warning: "FinanceData/Master.dat", line 6: Unknown account 'Liabilities:Tithe Owed' Warning: "FinanceData/Master.dat", line 8: Unknown account 'Liabilities:Tithe Owed' Warning: "FinanceData/Master.dat", line 15: Unknown account 'Allocation:Equities:Domestic' @end smallexample If you have a large Ledger register already created use the @command{accounts} command to get started: @smallexample @c command:validate $ ledger accounts >> Accounts.dat @end smallexample @noindent You will have to edit this file to add the @code{account} directive in front of every line. @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 supports many options, though typically the user can ignore most of them. They are summarized below. @menu * Transactions and Comments:: * Command Directives:: @end menu @node Transactions and Comments, Command Directives, Journal Format, Journal Format @subsection Transactions and Comments The initial character of each line determines what the line means, and how it should be interpreted. Allowable initial characters are: @table @code @item NUMBER A line beginning with a number denotes a transaction. It may be followed by any number of lines, each beginning with white-space, to denote the transaction's account postings. The format of the first line is: @smallexample DATE[=EDATE] [*|!] [(CODE)] DESC @end smallexample If @samp{*} 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 the transaction is ``pending''; i.e., tentatively cleared from the user's 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. The format of each following posting is: @smallexample ACCOUNT AMOUNT [; NOTE] @end smallexample 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 @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 @code{[ACTUAL_DATE]} or @code{[=EFFECTIVE_DATE]} or @code{[ACTUAL_DATE=EFFECTIVE_DATE]} (@pxref{Virtual postings}). @item P @findex --download @findex P @cindex historical prices Specifies a historical price for a commodity. These are usually found in a pricing history file (see the @option{--download (-Q)} option). The syntax is: @smallexample P DATE SYMBOL PRICE @end smallexample @item = @findex = @cindex automated transaction @cindex transaction, automated An automated transaction. A value expression must appear after the equal sign. After this initial line there should be a set of one or more postings, just as if it were a normal transaction. If the amounts of the postings have no commodity, they will be applied as multipliers to whichever real posting is matched by the value expression (@pxref{Automated Transactions}). @item ~ @findex ~ @cindex periodic transaction @cindex transaction, periodic A periodic transaction. A period expression must appear after the tilde. After this initial line there should be a set of one or more postings, just as if it were a normal transaction. @item ; # % | * @findex comment @cindex comments A line beginning with a semicolon, pound, percent, bar or asterisk indicates a comment, and is ignored. Comments will not be returned in a ``print'' response. @item indented ; @cindex tags If the semicolon is indented and occurs inside a transaction, it is parsed as a persistent note for its preceding category. These notes or tags can be used to augment the reporting and filtering capabilities of Ledger. @end table @node Command Directives, , Transactions and Comments, Journal Format @subsection Command Directives @findex --strict @findex --pedantic @table @code @item beginning of line Command directives must occur at the beginning of a line. Use of @samp{!} and @samp{@@} is deprecated. @item account @findex account @cindex pre-declare account Pre-declare valid account names. This only has an effect if @option{--strict} or @option{--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: @smallexample @c input:validate account Expenses:Food note This account is all about the chicken! alias food payee ^(KFC|Popeyes)$ check commodity == "$" assert commodity == "$" eval print("Hello!") default @end smallexample The @code{note} sub-directive associates a textual note with the account. This can be accessed later using the @code{note} value expression function in any account context. 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 @code{payee} sub-directive, which can occur multiple times, provides regexes that identify the account if that payee is encountered and an account within its transaction ends in the name "Unknown". Example: @smallexample @c input:validate 2012-02-27 KFC Expenses:Unknown $10.00 ; Read now as "Expenses:Food" Assets:Cash @end smallexample The @code{check} and @code{assert} directives warn or raise an error (respectively) if the given value expression evaluates to false within the context of any posting. 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 @code{default} directive specifies that this account should be used as the ``balancing account'' for any future transactions that contain only a single posting. @item apply account @findex apply account @c instance_t::master_account_directive Sets the root for all accounts following this directive. Ledger supports a hierarchical tree of accounts. It may be convenient to keep two ``root accounts''. For example you may be tracking your personal finances and your business finances. In order to keep them separate you could preface all personal accounts with @samp{personal:} and all business accounts with @samp{business:}. You can easily split out large groups of transactions without manually editing them using the account directive. For example: @smallexample @c input:validate apply account Personal 2011/11/15 Supermarket Expenses:Groceries $ 50.00 Assets:Checking @end smallexample Would result in all postings going into @samp{Personal:Expenses:Groceries} and @samp{Personal:Assets:Checking} until an @samp{end apply account} directive was found. @item alias @findex alias @cindex account, alias @c instance_t::alias_directive Define an alias for an account name. If you have a deeply nested tree of accounts, it may be convenient to define an alias, for example: @smallexample @c input:94A99E8 alias Dining=Expenses:Entertainment:Dining alias Checking=Assets:Credit Union:Joint Checking Account 2011/11/28 YummyPalace Dining $10.00 Checking @end smallexample The aliases are only in effect for transactions read in after the alias is defined and are affected by @code{account} directives that precede them. @smallexample @c command:94A99E8 $ ledger bal --no-total ^Exp @end smallexample @smallexample @c output:94A99E8 $10.00 Expenses:Entertainment:Dining @end smallexample With the option @option{--recursive-aliases}, aliases can refer to other aliases, the following example produces exactly the same transactions and account names as the preceding one: @smallexample @c input:83E1FB3 alias Entertainment=Expenses:Entertainment alias Dining=Entertainment:Dining alias Checking=Assets:Credit Union:Joint Checking Account 2011/11/30 ChopChop Dining $10.00 Checking @end smallexample @smallexample @c command:83E1FB3 $ ledger balance --no-total --recursive-aliases ^Exp @end smallexample @smallexample @c output:83E1FB3 $10.00 Expenses:Entertainment:Dining @end smallexample The option @option{--no-aliases} completely disables alias expansion. All accounts are read verbatim as they are in the ledger file. @item assert @findex assert @cindex assertions @c instance_t::assert_directive An assertion can throw an error if a condition is not met during Ledger's run. @smallexample assert <VALUE EXPRESSION BOOLEAN RESULT> @end smallexample @item bucket @anchor{bucket} @findex bucket @cindex bucket @c instance_t::default_account_directive Defines the default account to use for balancing transactions. Normally, each transaction has at least two postings, which must balance to zero. Ledger allows you to leave one posting with no amount and automatically balance the transaction in the posting. The @code{bucket} allows you to fill in all postings and automatically generate an additional posting to the bucket account balancing the transaction. If any transaction is unbalanced, it will automatically be balanced against the @code{bucket} account. The following example sets @samp{Assets:Checking} as the bucket: @smallexample @c input:validate bucket Assets:Checking 2011/01/25 Tom's Used Cars Expenses:Auto $ 5,500.00 2011/01/27 Book Store Expenses:Books $20.00 2011/12/01 Sale Assets:Checking:Business $ 30.00 @end smallexample @item capture @c instance_t::account_mapping_directive @findex capture @findex print @findex register Directs Ledger to replace any account matching a regex with the given account. For example: @smallexample @c input:validate capture Expenses:Deductible:Medical Medical @end smallexample Would cause any posting with @samp{Medical} in its name to be replaced with @samp{Expenses:Deductible:Medical}. Ledger will display the mapped payees in @command{print} and @command{register} reports. @item check @findex check @cindex assertions @c instance_t::check_directive in textual.cc A check issues a warning if a condition is not met during Ledger's run. @smallexample check <VALUE EXPRESSION BOOLEAN RESULT> @end smallexample @item comment @findex comment @cindex comments @c instance_t::comment_directive in textual.cc Start a block comment, closed by @code{end comment}. @item commodity @findex commodity @cindex pre-declare commodity Pre-declare commodity names. This only has an effect if @option{--strict} or @option{--pedantic} is used (see below). @smallexample @c input:validate commodity $ commodity CAD @end smallexample The @code{commodity} directive supports several optional sub-directives, if they immediately follow the commodity directive and---if they are on successive lines---begin with whitespace: @smallexample @c input:validate commodity $ note American Dollars format $1,000.00 nomarket default @end smallexample The @code{note} sub-directive associates a textual note with the commodity. At present this has no value other than documentation. The @code{format} sub-directive gives you a way to tell Ledger how to format this commodity. In the future, using this directive will disable Ledger's observation of other ways that commodity is used, and will provide the ``canonical'' representation. The @code{nomarket} sub-directive states that the commodity's price should never be auto-downloaded. The @code{default} sub-directive marks this as the ``default'' commodity. @item define @findex define @c instance_t::define_directive in textual.cc Allows you to define value expressions for future use. For example: @smallexample @c input:validate define var_name=$100 2011/12/01 Test Expenses (var_name*4) Assets @end smallexample The posting will have a cost of $400. @item end @findex end @c instance_t::end_directive in textual.cc Closes block commands like @code{tag} or @code{comment}. @item expr @findex expr @c instance_t::expr_directive in textual.cc @item fixed @findex fixed @cindex fixated prices @c instance_t::fixed_directive in textual.cc A fixed block is used to set fixated prices (@pxref{Fixated prices and costs}) for a series of transactions. It's purely a typing saver, for use when entering many transactions with fixated prices. Thus, the following: @smallexample @c input:validate 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 CAD @end smallexample is equivalent to this: @smallexample @c input:validate 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 @emph{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 @findex include @c instance_t::include_directive in textual.cc Include the stated file as if it were part of the current file. @item payee @findex payee @c instance_t::payee_alias_mapping_directive in textual.cc @c instance_t::payee_uuid_mapping_directive in textual.cc @findex print @findex register The @code{payee} directive supports two optional sub-directives, if they immediately follow the payee directive and---if it is on a successive line---begins with whitespace: @smallexample @c input:validate payee KFC alias KENTUCKY FRIED CHICKEN uuid 2a2e21d434356f886c84371eebac6e44f1337fda @end smallexample The @code{alias} sub-directive provides a regex which, if it matches a parsed payee, the declared payee name is substituted: @smallexample @c input:validate 2012-02-27 KENTUCKY FRIED CHICKEN ; will be read as being 'KFC' @end smallexample The @code{uuid} sub-directive specifies that a transaction with exactly the uuid given should have the declared payee name substituted: @smallexample @c input:validate 2014-05-13 UNHELPFUL PAYEE ; will be read as being 'KFC' ; UUID: 2a2e21d434356f886c84371eebac6e44f1337fda @end smallexample Ledger will display the mapped payees in @command{print} and @command{register} reports. @item apply tag @findex apply tag @c instance_t::tag_directive in textual.cc Allows you to designate a block of transactions and assign the same tag to all. Tags can have values and may be nested. @smallexample @c input:validate apply tag hastag apply tag nestedtag: true 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 end apply tag 2011/12/01 Sale Assets:Checking:Business $ 30.00 Income:Sales end apply tag @end smallexample @noindent is the equivalent of: @smallexample @c input:validate 2011/01/25 Tom's Used Cars ; :hastag: ; nestedtag: true Expenses:Auto $ 5,500.00 ; :nobudget: Assets:Checking 2011/01/27 Book Store ; :hastag: ; nestedtag: true Expenses:Books $20.00 Liabilities:MasterCard 2011/12/01 Sale ; :hastag: Assets:Checking:Business $ 30.00 Income:Sales @end smallexample @c TODO: the following paragraph seems to be false, the automated tests @c fail, if anything appears after end apply tag. @c Note that anything following @code{end apply tag} is ignored. Placing @c the name of the tag that is being closed is a simple way to keep @c track. @item tag @findex tag @cindex pre-declare tag Pre-declares tag names. This only has an effect if @option{--strict} or @option{--pedantic} is used (see below). @smallexample @c input:validate tag Receipt tag CSV @end smallexample The @code{tag} directive supports two optional sub-directives, if they immediately follow the tag directive and---if on a successive line---begin with whitespace: @smallexample @c input:validate tag Receipt check value =~ /pattern/ assert value != "foobar" @end smallexample The @code{check} and @code{assert} sub-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 be something else but a string if typed metadata is used!). Such checks or assertions are not called if no value is given. @item test @findex test @cindex comments @c instance_t::comment_directive in textual.cc This is a synonym for @code{comment} and must be closed by an @code{end} tag. @item year @findex year @anchor{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 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. @end table The following single letter commands may be at the beginning of a line alone, for backwards compatibility with older Ledger versions. @table @code @item A @findex A @findex bucket @xref{bucket}. @item Y @findex Y @findex year @xref{year}. @item N SYMBOL @findex N Indicates that pricing information is to be ignored for a given symbol, nor will quotes ever be downloaded for that symbol. Useful with a home currency, such as the dollar @samp{$}. It is recommended that these pricing options be set in the price database file, which defaults to @file{~/.pricedb}. The syntax for this command is: @smallexample @c input:validate N SYMBOL @end smallexample @item D AMOUNT @findex xact @findex D Specifies the default commodity to use, by specifying an amount in the expected format. The @command{xact} command will use this commodity as the default when none other can be determined. This command may be used multiple times, to set the default flags for different commodities; whichever is seen last is used as the default commodity. For example, to set US dollars as the default commodity, while also setting the thousands flag and decimal flag for that commodity, use: @smallexample @c input:validate D $1,000.00 @end smallexample @item C AMOUNT1 = AMOUNT2 @findex C Specifies a commodity conversion, where the first amount is given to be equivalent to the second amount. The first amount should use the decimal precision desired during reporting: @smallexample @c input:validate C 1.00 Kb = 1024 bytes @end smallexample @item I, i, O, o, b, h @findex I @findex i @findex O @findex o @findex b @findex h These four relate to timeclock support, which permits Ledger to read timelog files. See timeclock's documentation for more info on the syntax of its timelog files. @end table @node Converting from other formats, Archiving Previous Years, Journal Format, Keeping a Journal @section Converting from other formats @cindex csv importing There are numerous tools to help convert various formats to a Ledger file. Most banks will generate a comma separated values file that can easily be parsed into Ledger format using one of those tools. Some of the most popular tools are: @itemize @item @code{ledger convert download.csv} @item @code{hledger -f checking.csv print} @item @uref{https://github.com/quentinsf/icsv2ledger, @code{icsv2ledger}} @item @uref{https://github.com/tazzben/csvToLedger, @code{csvToLedger}} @item @uref{https://launchpad.net/csv2ledger, @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 @findex equity @findex print After a while, your journal can get to be pretty large. While this will not slow down Ledger---it's designed to process journals very quickly---things can start to feel ``messy''; and it's a universal complaint that when finances feel messy, people avoid them. Thus, archiving the data from previous years into their own files can offer a sense of completion, and freedom from the past. But how to best accomplish this with the ledger program? There are two commands that make it very simple: @command{print}, and @command{equity}. Let's take an example file, with data ranging from year 2000 until 2004. We want to archive years 2000 and 2001 to their own file, leaving just 2003 and 2004 in the current file. So, use @command{print} to output all the earlier transactions to a file called @file{ledger-old.dat}: @smallexample $ ledger -f ledger.dat -b 2000 -e 2001 print > ledger-old.dat @end smallexample To delete older data from the current ledger file, use @command{print} again, this time specifying year 2002 as the starting date: @smallexample $ ledger -f ledger.dat -b 2002 print > x $ mv x ledger.dat @end smallexample However, now the current file contains @emph{only} postings from 2002 onward, which will not yield accurate present-day balances, because the net income from previous years is no longer being tallied. To compensate for this, we must append an equity report for the old ledger at the beginning of the new one: @smallexample $ ledger -f ledger-old.dat equity > equity.dat $ cat equity.dat ledger.dat > x $ mv x ledger.dat $ rm equity.dat @end smallexample Now the balances reported from @file{ledger.dat} are identical to what they were before the data was split. How often should you split your ledger? You never need to, if you don't want to. Even eighty years of data will not slow down ledger much, and that's just using present day hardware! Or, you can keep the previous and current year in one file, and each year before that in its own file. It's really up to you, and how you want to organize your finances. For those who also keep an accurate paper trail, it might be useful to archive the older years to their own files, then burn those files to a CD to keep with the paper records---along with any electronic statements received during the year. In the arena of organization, just keep in mind this maxim: Do whatever keeps you doing it. @node Transactions, Building Reports, Keeping a Journal, Top @chapter Transactions @menu * Basic format:: * Eliding amounts:: * Auxiliary dates:: * Codes:: * Transaction state:: * Transaction notes:: * Metadata:: * Virtual postings:: * Expression amounts:: * Balance verification:: * Posting cost:: * Explicit posting costs:: * Posting cost expressions:: * Total posting costs:: * Virtual posting costs:: * Commodity prices:: * Prices versus costs:: * Fixated prices and costs:: * Lot dates:: * Lot notes:: * Lot value expressions:: * Automated Transactions:: @end menu @node Basic format, Eliding amounts, Transactions, Transactions @section Basic format The most basic form of transaction is: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash $-20.00 @end smallexample This transaction has a date, a payee or description, a target account (the first posting), and a source account (the second posting). Each posting specifies what action is taken related to that account. A transaction can have any number of postings: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash $-10.00 Liabilities:Credit $-10.00 @end smallexample @node Eliding amounts, Auxiliary dates, Basic format, Transactions @section Eliding amounts The first thing you can do to make things easier is elide amounts. That is, if exactly one posting has no amount specified, Ledger will infer the inverse of the other postings' amounts: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash $-10.00 Liabilities:Credit ; same as specifying $-10 @end smallexample @noindent If the other postings use multiple commodities, Ledger will copy the empty posting N times and fill in the negated values of the various commodities: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Expenses:Tips $2.00 Assets:Cash EUR -10.00 Assets:Cash GBP -10.00 Liabilities:Credit @end smallexample @noindent This transaction is identical to writing: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Expenses:Tips $2.00 Assets:Cash EUR -10.00 Assets:Cash GBP -10.00 Liabilities:Credit $-22.00 Liabilities:Credit EUR 10.00 Liabilities:Credit GBP 10.00 @end smallexample @node Auxiliary dates, Codes, Eliding amounts, Transactions @section Auxiliary dates @findex --aux-date You can associate a second date with a transaction by following the primary date with an equals sign: @smallexample @c input:validate 2012-03-10=2012-03-08 KFC Expenses:Food $20.00 Assets:Cash $-20.00 @end smallexample What this auxiliary date means is entirely up to you. The only use Ledger has for it is that if you specify @option{--aux-date}, then all reports and calculations (including pricing) will use the auxiliary date as if it were the primary date. @node Codes, Transaction state, Auxiliary dates, Transactions @section Codes A transaction can have a textual ``code''. This has no meaning and is only displayed by the print command. Checking accounts often use codes like DEP, XFER, etc., as well as check numbers. This is to give you a place to put those codes: @smallexample @c input:validate 2012-03-10 (#100) KFC Expenses:Food $20.00 Assets:Checking @end smallexample @node Transaction state, Transaction notes, Codes, Transactions @section Transaction state @findex --cleared @findex --uncleared @findex --pending A transaction can have a ``state'': cleared, pending, or uncleared. The default is uncleared. To mark a transaction cleared, put an asterisk @samp{*} before the payee, after the date or code: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash @end smallexample @noindent To mark it pending, use a @samp{!}: @smallexample @c input:validate 2012-03-10 ! KFC Expenses:Food $20.00 Assets:Cash @end smallexample What these mean is entirely up to you. The @option{--cleared} option limits reports to only cleared items, while @option{--uncleared} shows both uncleared and pending items, and @option{--pending} shows only pending items. I use cleared to mean that I've reconciled the transaction with my bank statement, and pending to mean that I'm in the middle of a reconciliation. When you clear a transaction, that's really just shorthand for clearing all of its postings. That is: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash @end smallexample @noindent Is the same as writing: @smallexample @c input:validate 2012-03-10 KFC * Expenses:Food $20.00 * Assets:Cash @end smallexample @noindent You can mark individual postings as cleared or pending, in case one ``side'' of the transaction has cleared, but the other hasn't yet: @smallexample @c input:validate 2012-03-10 KFC Liabilities:Credit $100.00 * Assets:Checking @end smallexample @node Transaction notes, Metadata, Transaction state, Transactions @section Transaction notes After the payee, and after at least one tab or two spaces (or a space and a tab, which Ledger calls a ``hard separator''), you may introduce a note about the transaction using the @samp{;} character: @smallexample @c input:validate 2012-03-10 * KFC ; yum, chicken... Expenses:Food $20.00 Assets:Cash @end smallexample @noindent Notes can also appear on the next line, so long as that line begins with whitespace: @smallexample @c input:validate 2012-03-10 * KFC ; yum, chicken... ; and more notes... Expenses:Food $20.00 Assets:Cash 2012-03-10 * KFC ; just these notes... Expenses:Food $20.00 Assets:Cash @end smallexample A transaction's note is shared by all its postings. This becomes significant when querying for metadata (see below). To specify that a note belongs only to one posting, place it after a hard separator after the amount, or on its own line preceded by whitespace: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 ; posting #1 note Assets:Cash ; posting #2 note, extra indentation is optional @end smallexample @node Metadata, Virtual postings, Transaction notes, Transactions @section Metadata @c TODO add cindex @c TODO https://groups.google.com/d/msg/ledger-cli/2csLPcHJ3ak/a17jOClzLTUJ @c > Is there a way to produce a register report that lists all the transaction @c > that contain a certain tag, and sort them based on the value of the tag? @c ledger reg --sort "tag('foo')" %foo @c ledger reg --group-by "tag('Employer)" Remboursement:Employer and tag Employer @c > Is it possible to get subtotals for each tag value? @c ledger --group-by "tag('foo')" bal @c TODO https://groups.google.com/d/msg/ledger-cli/K9NBhNlVnYc/TDYDAWhOA5EJ One of Ledger's more powerful features is the ability to associate typed metadata with postings and transactions (by which I mean all of a transaction's postings). This metadata can be queried, displayed, and used in calculations. The are two forms of metadata: plain tags, and tag/value pairs. @menu * Metadata tags:: * Metadata values:: * Typed metadata:: @end menu @node Metadata tags, Metadata values, Metadata, Metadata @subsection Metadata tags To tag an item, put any word not containing whitespace between two colons inside a comment: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash ; :TAG: @end smallexample You can gang up multiple tags by sharing colons: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash ; :TAG1:TAG2:TAG3: @end smallexample @menu * Payee metadata tag:: @end menu @node Payee metadata tag, , Metadata tags, Metadata tags @subsubsection Payee metadata tag @cindex Payee metadata tag @findex register @findex payees @findex --by-payee ``Payee'' is a special metadata field. If set on a posting, it will be used as the payee name for that posting. This affects the @command{register} report, the @command{payees} report, and the @option{--by-payee} option. This is useful when for example you deposit 4 checks at a time to the bank. On the bank statement, there is just one amount @samp{$400}, but you can specify from whom each check came from, as shown by example below: @smallexample @c input:9B43E57 2010-06-17 Sample Assets:Bank $400.00 Income:Check1 $-100.00 ; Payee: Person One Income:Check2 $-100.00 ; Payee: Person Two Income:Check3 $-100.00 ; Payee: Person Three Income:Check4 $-100.00 ; Payee: Person Four @end smallexample When reporting with @smallexample @c command:9B43E57 $ ledger reg @end smallexample it appears as: @smallexample @c output:9B43E57 10-Jun-17 Sample Assets:Bank $400.00 $400.00 Person One Income:Check1 $-100.00 $300.00 Person Two Income:Check2 $-100.00 $200.00 Person Three Income:Check3 $-100.00 $100.00 Person Four Income:Check4 $-100.00 0 @end smallexample This shows that they are all in the same transaction (which is why the date is not repeated), but they have different payees now. @node Metadata values, Typed metadata, Metadata tags, Metadata @subsection Metadata values To associate a value with a tag, use the syntax ``Key: Value'', where the value can be any string of characters. Whitespace is needed after the colon, and cannot appear in the Key: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash ; MyTag: This is just a bogus value for MyTag @end smallexample @node Typed metadata, , Metadata values, Metadata @subsection Typed metadata 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 @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash ; 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: @smallexample 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash ; 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. @node Virtual postings, Expression amounts, Metadata, Transactions @section Virtual postings @findex --real Ordinarily, the amounts of all postings in a transaction must balance to zero. This is non-negotiable. It's what double-entry accounting is all about! But 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 @option{--real}. To specify a virtual account, surround the account name with parentheses: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash (Budget:Food) $-20.00 @end smallexample If you want, you can state that virtual postings @emph{should} balance against one or more other virtual postings by using brackets (which look ``harder'') rather than parentheses: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food $20.00 Assets:Cash [Budget:Food] $-20.00 [Equity:Budgets] $20.00 @end smallexample @node Expression amounts, Balance verification, Virtual postings, Transactions @section Expression amounts An amount is usually a numerical figure with an (optional) commodity, but it can also be any value expression. To indicate this, surround the amount expression with parentheses: @smallexample @c input:validate 2012-03-10 * KFC Expenses:Food ($10.00 + $20.00) ; Ledger adds it up for you Assets:Cash @end smallexample @node Balance verification, Posting cost, Expression amounts, Transactions @section Balance verification @findex --permissive @menu * Balance assertions:: * Balance assignments:: * Resetting a balance:: * Balancing transactions:: @end menu If at the end of a posting's amount (and after the cost too, if there is one) there is an equals sign, then Ledger will verify that the total value for that account as of that posting matches the amount specified. See @option{--permissive} option to relax the balance assertions checks. There are two forms of this features: balance assertions, and balance assignments. @node Balance assertions, Balance assignments, Balance verification, Balance verification @subsection Balance assertions A balance assertion has this general form: @smallexample 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash $-20.00 = $500.00 @end smallexample This simply asserts that after subtracting $20.00 from Assets:Cash, that the resulting total matches $500.00. If not, it is an error. @node Balance assignments, Resetting a balance, Balance assertions, Balance verification @subsection Balance assignments A balance assignment has this form: @smallexample 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash = $500.00 @end smallexample This sets the amount of the second posting to whatever it would need to be for the total in @samp{Assets:Cash} to be $500.00 after the posting. If the resulting amount is not $-20.00 in this case, it is an error. @node Resetting a balance, Balancing transactions, Balance assignments, Balance verification @subsection Resetting a balance Say your book-keeping has gotten a bit out of date, and your Ledger balance no longer matches your bank balance. You can create an adjustment transaction using balance assignments: @smallexample @c input:validate 2012-03-10 Adjustment Assets:Cash = $500.00 Equity:Adjustments @end smallexample Since the second posting is also null, it's value will become the inverse of whatever amount is generated for the first posting. This is the only time in ledger when more than one posting's amount may be empty---and then only because it's not truly empty, it is indirectly provided by the balance assignment's value. @node Balancing transactions, , Resetting a balance, Balance verification @subsection Balancing transactions @findex --empty As a consequence of all the above, consider the following transaction: @smallexample 2012-03-10 My Broker [Assets:Brokerage] = 10 AAPL @end smallexample What this says is: set the amount of the posting to whatever value is needed so that @samp{Assets:Brokerage} contains 10 AAPL. Then, because this posting must balance, ensure that its value is zero. This can only be true if Assets:Brokerage does indeed contain 10 AAPL at that point in the input file. A balanced virtual transaction is used simply to indicate to Ledger that this is not a ``real'' transaction. It won't appear in any reports anyway (unless you use a register report with @option{--empty}). @node Posting cost, Explicit posting costs, Balance verification, Transactions @section Posting cost When you transfer a commodity from one account to another, sometimes it gets transformed during the transaction. This happens when you spend money on gas, for example, which transforms dollars into gallons of gasoline, or dollars into stocks in a company. In those cases, Ledger will remember the ``cost'' of that transaction for you, and can use it during reporting in various ways. Here's an example of a stock purchase: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL Assets:Brokerage:Cash $-500.00 @end smallexample This is different from transferring 10 AAPL shares from one account to another, in this case you are @emph{exchanging} one commodity for another. The resulting posting's 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 @samp{@@} symbol after the amount or amount expression: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $-500.00 @end smallexample When you do this, since Ledger can now figure out the balancing amount from the first posting's cost, you can elide the other amount: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash @end smallexample @menu * Primary and secondary commodities:: @end menu @node Primary and secondary commodities, , Explicit posting costs, Explicit posting costs @subsection Primary and secondary commodities @findex --market @findex --exchange @var{COMMODITY} It is a general convention within Ledger that the ``top'' postings in a transaction contain the target accounts, while the final posting contains the 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 @@ AMOUNT", the commodity used in the second amount is marked ``primary''. The only meaning a primary commodity has is that the @option{--market (-V)} flag will never convert a primary commodity into any other commodity. @option{--exchange @var{COMMODITY} (-X)} still will, however. @node Posting cost expressions, Total posting costs, Explicit posting costs, Transactions @section Posting cost expressions Just as you can have amount expressions, you can have posting expressions: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL @@ ($500.00 / 10) Assets:Brokerage:Cash @end smallexample You can even have both: @smallexample @c input:validate 2012-03-10 My Broker 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 @samp{@@} character specifies the @emph{per-unit} price for the commodity being transferred. If you'd like to specify the total cost instead, use @samp{@@@@}: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL @@@@ $500.00 Assets:Brokerage:Cash @end smallexample Ledger reads this as if you had written: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL @@ ($500.00 / 10) Assets:Brokerage:Cash @end smallexample @node Virtual posting costs, Commodity prices, Total posting costs, Transactions @section Virtual posting costs Normally whenever a commodity exchange like this happens, the price of the exchange (such as $50 per share of AAPL, above) is recorded in Ledger's internal price history database. To prevent this from happening in the case of an exceptional transaction, surround the @samp{@@} or @samp{@@@@} with parentheses: @smallexample @c input:validate 2012-03-10 My Brother Assets:Brokerage 1000 AAPL (@@) $1 Income:Gifts Received @end smallexample @node Commodity prices, Prices versus costs, Virtual posting costs, Transactions @section Commodity prices @findex --lot-prices When a transaction occurs that exchanges 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 @option{--lot-prices} to show these hidden price figures. For example, consider the stock sale given above: @smallexample @c input:validate 2012-03-10 My Broker Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash @end smallexample The commodity transferred into @samp{Assets:Brokerage} is not actually 10 AAPL, but rather 10 AAPL @{$50.00@}. The figure in braces after the amount is called the ``lot price''. It's Ledger's way of remembering that this commodity was transferred through an exchange, and that $50.00 was the price of that exchange. This becomes significant if you later sell that commodity again. For example, you might write this: @smallexample @c input:validate 2012-04-10 My Broker Assets:Brokerage:Cash Assets:Brokerage -10 AAPL @@ $75.00 @end smallexample And that would be perfectly fine, but how do you track the capital gains on the sale? It could be done with a virtual posting: @smallexample @c input:validate 2012-04-10 My Broker Assets:Brokerage:Cash Assets:Brokerage -10 AAPL @@ $75.00 (Income:Capital Gains) $-250.00 @end smallexample But this gets messy since capital gains income is very real, and not quite appropriate for a virtual posting. Instead, if you reference that same hidden price annotation, Ledger will figure out that the price of the shares you're selling, and the cost you're selling them at, don't balance: @smallexample 2012-04-10 My Broker Assets:Brokerage:Cash $750.00 Assets:Brokerage -10 AAPL @{$50.00@} @@ $75.00 @end smallexample This transaction will fail because the $250.00 price difference between the price you bought those shares at, and the cost you're selling them for, does not match. The lot price also identifies which shares you purchased on that prior date. @menu * Total commodity prices:: @end menu @node Total commodity prices, , Commodity prices, Commodity prices @subsection Total commodity prices As a shorthand, you can specify the total price instead of the per-share price in doubled braces. This goes well with total costs, but is not required to be used with them: @smallexample @c input:validate 2012-04-10 My Broker Assets:Brokerage:Cash $750.00 Assets:Brokerage -10 AAPL @{@{$500.00@}@} @@@@ $750.00 Income:Capital Gains $-250.00 @end smallexample It should be noted that this is a convenience only for cases where you buy and sell whole lots. The @{@{$500.00@}@} is @emph{not} an attribute of the commodity, whereas @{$50.00@} is. In fact, when you write @{@{$500.00@}@}, Ledger just divides that value by 10 and sees @{$50.00@}. So if you use the print command to look at this transaction, you'll see the single braces form in the output. The double braces price form is a shorthand only. Plus, it comes with dangers. This works fine: @smallexample @c input:validate 2012-04-10 My Broker Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $-500.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{$50.00@} @@ $375.00 Income:Capital Gains $-125.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{$50.00@} @@ $375.00 Income:Capital Gains $-125.00 @end smallexample @noindent But this does not do what you might expect: @smallexample 2012-04-10 My Broker Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $750.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@ $375.00 Income:Capital Gains $-125.00 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@ $375.00 Income:Capital Gains $-125.00 @end smallexample And in cases where the amounts do not divide into whole figures and must be rounded, the capital gains figure could be off by a cent. Use with caution. @node Prices versus costs, Fixated prices and costs, Commodity prices, Transactions @section Prices versus costs Because lot pricing provides enough information to infer the cost, the following two transactions are equivalent: @smallexample 2012-04-10 My Broker Assets:Brokerage 10 AAPL @@ $50.00 Assets:Brokerage:Cash $750.00 2012-04-10 My Broker Assets:Brokerage 10 AAPL @{$50.00@} Assets:Brokerage:Cash $750.00 @end smallexample 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. @node Fixated prices and costs, Lot dates, Prices versus 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 can short-circuit this lookup by ``fixing'' the price at the time of a transaction. This is done using @samp{@{=AMOUNT@}}: @smallexample 2012-04-10 My Broker Assets:Brokerage 10 AAPL @{=$50.00@} Assets:Brokerage:Cash $750.00 @end smallexample These 10 AAPL will now always be reported as being worth $50, no matter what 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. 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: @smallexample 2012-04-10 My Broker Assets:Brokerage 10 AAPL @@ =$50.00 Assets:Brokerage:Cash $750.00 @end smallexample This is the same as the previous transaction, with the same caveats found in @ref{Prices versus costs}. @node Lot dates, Lot notes, Fixated prices and costs, Transactions @section Lot dates @findex --lot-dates In addition to lot prices, you can specify lot dates and reveal them with @option{--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): @smallexample 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] @@ $375.00 Income:Capital Gains $-125.00 @end smallexample @node Lot notes, Lot value expressions, Lot dates, Transactions @section Lot notes @findex --lot-notes @findex --lots You can also associate arbitrary notes for your own record keeping in parentheses, and reveal them with @option{--lot-notes}. One caveat is that the note cannot begin with an @samp{@@} character, as that would indicate a virtual cost: @smallexample 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] (Oh my!) @@ $375.00 Income:Capital Gains $-125.00 @end smallexample You can specify any combination of lot prices, dates or notes, in any order. They are all optional. To show all lot information in a report, use @option{--lots}. @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 uses a value expression called ``market'' to determine the most recent value from its price database---even downloading prices from the Internet, if @option{--download (-Q)} was specified and a suitable @file{getquote} script is found on your system. However, you can override this valuation logic by providing a commodity valuation expression in doubled parentheses. This expression must result in one of two values: either an amount to always be used as the per-share price for that commodity; or a function taking three arguments, which is called to determine that price. If you use the functional form, you can either specify a function name, or a lambda expression. Here's a function that yields the price as $10 in whatever commodity is being requested: @smallexample @c input:validate define ten_dollars(s, date, t) = market($10, date, t) @end smallexample I can now use that in a lot value expression as follows: @smallexample @c input:validate 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 Assets:Brokerage -5 AAPL @{$50.00@} ((ten_dollars)) @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample Alternatively, I could do the same thing without pre-defining a function by using a lambda expression taking three arguments: @smallexample 2012-04-10 My Broker A:B:Cash $375.00 A:B -5 AAPL @{$50.00@} ((s, d, t -> market($10, date, t))) @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample The arguments passed to these functions have the following meaning: @itemize @item source The source commodity string, or an amount object. If it is a string, the return value must be an amount representing the price of the commodity identified by that string (example: @samp{$}). If it is an amount, return the value of that amount as a new amount (usually calculated as commodity price times source amount). @item date The date to use for determining the value. If null, it means no date was specified, which can mean whatever you want it to mean. @item target If not null, a string representing the desired target commodity that the commodity price, or repriced amount, should be valued in. Note that this string can be a comma-separated list, and that some or all of the commodities in that list may be suffixed with an exclamation mark, to indicate what is being desired. @end itemize In most cases, it is simplest to either use explicit amounts in your valuation expressions, or just pass the arguments down to @samp{market} after modifying them to suit your needs. @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' postings matches its predicate. The predicate uses the same query syntax as the Ledger command-line. Consider this posting: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash @end smallexample If I write this automated transaction before it in the file: @smallexample @c input:validate = expr true Foo $50.00 Bar $-50.00 @end smallexample Then the first transaction will be modified during parsing as if I'd written this: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Foo $50.00 Bar $-50.00 Assets:Cash $-20.00 Foo $50.00 Bar $-50.00 @end smallexample Despite this fancy logic, automated transactions themselves follow most of the same rules as regular transactions: their postings must balance (unless you use a virtual posting), you can have metadata, etc. One thing you cannot do, however, is elide amounts in an automated transaction. @menu * Amount multipliers:: * Accessing the matching posting's amount:: * Referring to the matching posting's account:: * Applying metadata to every matched posting:: * Applying metadata to the generated posting:: * 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 @subsection Amount multipliers As a special case, if an automated transaction's posting's amount (phew) has no commodity, it is taken as a multiplier upon the matching posting's cost. For example: @smallexample @c input:validate = expr true Foo 50.00 Bar -50.00 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash @end smallexample Then the latter transaction turns into this during parsing: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 Foo $1000.00 Bar $-1000.00 Assets:Cash $-20.00 Foo $1000.00 Bar $-1000.00 @end smallexample @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 expression has access to all the details of the matched posting. For example, you can refer to that posting's amount using the ``amount'' value expression variable: @smallexample @c input:validate = expr true (Foo) (amount * 2) ; same as just "2" in this case 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash @end smallexample This becomes: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 (Foo) $40.00 Assets:Cash $-20.00 (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 @subsection Referring to the matching posting's account Sometimes you want to refer to the account that was matched in some way within the automated transaction itself. This is done by using the string @samp{$account}, anywhere within the account part of the automated posting: @smallexample @c input:validate = food (Budget:$account) 10 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash @end smallexample Becomes: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 (Budget:Expenses:Food) $200.00 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 @subsection Applying metadata to every matched posting If the automated transaction has a transaction note, that note is copied (along with any metadata) to every posting that matches the predicate: @smallexample @c input:validate = food ; Foo: Bar (Budget:$account) 10 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash @end smallexample Becomes: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 ; Foo: Bar (Budget:Expenses:Food) $200.00 Assets:Cash $-20.00 @end smallexample @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 generated posting within the matched transaction: @smallexample @c input:validate = food (Budget:$account) 10 ; Foo: Bar 2012-03-10 KFC Expenses:Food $20.00 Assets:Cash @end smallexample Becomes: @smallexample @c input:validate 2012-03-10 KFC Expenses:Food $20.00 (Budget:Expenses:Food) $200.00 ; Foo: Bar Assets:Cash $-20.00 @end smallexample 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 @subsection State flags Although you cannot mark an automated transaction as a whole as cleared or pending, you can mark its postings with a @samp{*} or @samp{!} before the account name, and that state flag gets carried to the generated posting. @node Effective Dates, Periodic Transactions, State flags, Automated Transactions @subsection Effective Dates @cindex effective dates @findex --effective In the real world, transactions do not take place instantaneously. Purchases can take several days to post to a bank account. And you may pay ahead for something for which you want to distribute costs. With Ledger you can control every aspect of the timing of a transaction. Say you're in business. If you bill a customer, you can enter something like @cindex effective date of invoice @smallexample @c input:validate 2008/01/01=2008/01/14 Client invoice ; estimated date you'll be paid Assets:Accounts Receivable $100.00 Income: Client name @end smallexample Then, when you receive the payment, you change it to @smallexample @c input:validate 2008/01/01=2008/01/15 Client invoice ; actual date money received Assets:Accounts Receivable $100.00 Income: Client name @end smallexample @noindent and add something like @smallexample @c input:validate 2008/01/15 Client payment Assets:Checking $100.00 Assets:Accounts Receivable @end smallexample Now @smallexample @c command:validate $ ledger --begin 2008/01/01 --end 2008/01/14 bal Income @end smallexample @noindent gives you your accrued income in the first two weeks of the year, and @smallexample @c command:validate $ ledger --effective --begin 2008/01/01 --end 2008/01/14 bal Income @end smallexample @noindent gives you your cash basis income in the same two weeks. Another use is distributing costs out in time. As an example, suppose you just prepaid into a local vegetable co-op that sustains you through the winter. It costs $225 to join the program, so you write a check. You don't want your October grocery budget to be blown because you bought food ahead, however. What you really want is for the money to be evenly distributed over the next six months so that your monthly budgets gradually take a hit for the vegetables you'll pick up from the co-op, even though you've already paid for them. @smallexample @c input:6453542 2008/10/16 * (2090) Bountiful Blessings Farm Expenses:Food:Groceries $ 37.50 ; [=2008/10/01] Expenses:Food:Groceries $ 37.50 ; [=2008/11/01] Expenses:Food:Groceries $ 37.50 ; [=2008/12/01] Expenses:Food:Groceries $ 37.50 ; [=2009/01/01] Expenses:Food:Groceries $ 37.50 ; [=2009/02/01] Expenses:Food:Groceries $ 37.50 ; [=2009/03/01] Assets:Checking @end smallexample This entry accomplishes this. Every month you'll see an automatic $37.50 deficit like you should, while your checking account really knows that it debited $225 this month. And using the @option{--effective} option, the initial date will be overridden by the effective dates. @smallexample @c command:6453542 $ ledger --effective register Groceries @end smallexample @smallexample @c output:6453542 08-Oct-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 37.50 08-Nov-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 75.00 08-Dec-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 112.50 09-Jan-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 150.00 09-Feb-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 187.50 09-Mar-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 225.00 @end smallexample @node Periodic Transactions, Concrete Example of Automated Transactions, Effective Dates, Automated Transactions @subsection Periodic Transactions @findex --budget A periodic transaction starts with a tilde @samp{~} followed by a period expression (see @ref{Period Expressions}). Periodic transactions are used for budgeting and forecasting only, they have no effect without the @option{--budget} option specified. For examples and details, @pxref{Budgeting and Forecasting}. @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 @c input:C371854 ; This automated transaction will compute Huqúqu'lláh based on this ; journal's postings. Any accounts 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. @smallexample @c input:C371854 2003/01/01 (99) Salary Income:Salary -$1000 Assets:Checking 2003/01/01 (100) Rent Expenses:Rent $500 Assets:Checking @end smallexample 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 @c input:validate 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 @c command:C371854 $ ledger balance Liabilities:Huquq @end smallexample @smallexample @c output:C371854 $-95 Liabilities:Huququ'llah @end smallexample This works fine, but omits one aspect of the law: that Huqúq 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: @c TODO: fix this, it doesn't work any longer @smallexample $ ledger -Q -t "/Liab.*Huquq/?(a/P@{2.22 AU@}<=@{-1.0@}&a):a" 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: @c TODO: fix this, it doesn't work any longer @smallexample $ ledger -Q -T "/Liab.*Huquq/?(O/P@{2.22 AU@}<=@{-1.0@}&O):O" 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 @c input:validate = /^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}. @node Building Reports, Reporting Commands, Transactions, Top @chapter Building Reports @menu * Introduction:: * Balance Reports:: * Typical queries:: * Advanced Reports:: @end menu @node Introduction, Balance Reports, Building Reports, Building Reports @section Introduction The power of Ledger comes from the incredible flexibility in its reporting commands, combined with formatting commands. Some options control what is included in the calculations, and formatting controls how it is displayed. The combinations are infinite. This chapter will show you the basics of combining various options and commands. In the next chapters you will find details about the specific commands and options. @node Balance Reports, Typical queries, Introduction, Building Reports @section Balance Reports @menu * Controlling the Accounts and Payees:: * Controlling Formatting:: @end menu @node Controlling the Accounts and Payees, Controlling Formatting, Balance Reports, Balance Reports @subsection Controlling the Accounts and Payees The balance report is the most commonly used report. The simplest invocation is: @smallexample @c command:1D00D56 $ ledger balance -f drewr3.dat @end smallexample @noindent which will print the balances of every account in your journal. @smallexample @c output:1D00D56 $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business $ -5,200.00 Savings $ -1,000.00 Equity:Opening Balances $ 6,654.00 Expenses $ 5,500.00 Auto $ 20.00 Books $ 300.00 Escrow $ 334.00 Food:Groceries $ 500.00 Interest:Mortgage $ -2,030.00 Income $ -2,000.00 Salary $ -30.00 Sales $ -63.60 Liabilities $ -20.00 MasterCard $ 200.00 Mortgage:Principal $ -243.60 Tithe -------------------- $ -243.60 @end smallexample Most times, this is more than you want. Limiting the results to specific accounts is as easy as entering the names of the accounts after the command: @smallexample @c command:06B2AD4 $ ledger balance -f drewr3.dat Auto MasterCard @end smallexample @smallexample @c output:06B2AD4 $ 5,500.00 Expenses:Auto $ -20.00 Liabilities:MasterCard -------------------- $ 5,480.00 @end smallexample @noindent Note the implicit logical or between @samp{Auto} and @samp{Mastercard}. If you want the entire contents of a branch of your account tree, use the highest common name in the branch: @smallexample @c command:B0468E1 $ ledger balance -f drewr3.dat Income @end smallexample @smallexample @c output:B0468E1 $ -2,030.00 Income $ -2,000.00 Salary $ -30.00 Sales -------------------- $ -2,030.00 @end smallexample You can use general regular expressions in nearly any place Ledger needs a string: @smallexample @c command:EAE389F $ ledger balance -f drewr3.dat ^Bo @end smallexample @smallexample @c output:EAE389F @end smallexample This first example looks for any account starting with @samp{Bo}, of which there are none. @smallexample @c command:E2AF6AD $ ledger balance -f drewr3.dat Bo @end smallexample @smallexample @c output:E2AF6AD $ 20.00 Expenses:Books @end smallexample This second example looks for any account containing @samp{Bo}, which is @samp{Expenses:Books}. @cindex limit by payees @findex --limit @var{EXPR} If you want to know exactly how much you have spent in a particular account on a particular payee, the following are equivalent: @smallexample @c command:validate $ ledger balance Auto:Fuel and Chevron @end smallexample @smallexample @c command:validate $ ledger balance --limit 'account=~/Fuel/' and 'payee=~/Chev/' @end smallexample @noindent will show you the amount expended on gasoline at Chevron. The second example is the first example of the very powerful expression language available to shape reports. The first example may be easier to remember, but learning to use the second will open up far more possibilities. If you want to exclude specific accounts from the report, you can exclude multiple accounts with parentheses: @smallexample @c command:validate $ ledger bal Expenses and not (Expenses:Drinks or Expenses:Candy or Expenses:Gifts) @end smallexample @node Controlling Formatting, , Controlling the Accounts and Payees, Balance Reports @subsection Controlling Formatting These examples all use the default formatting for the balance report. Customizing the formatting can easily allowing to see only what you want, or interface Ledger with other programs. @node Typical queries, Advanced Reports, Balance Reports, Building Reports @section Typical queries A query such as the following shows all expenses since last October, sorted by total: @smallexample @c command:validate $ ledger -b "last oct" -S T bal ^expenses @end smallexample From left to right the options mean: Show transactions since last October; sort by the absolute value of the total; and report the balance for all accounts that begin with @samp{expenses}. @menu * Reporting monthly expenses:: @end menu @node Reporting monthly expenses, , Typical queries, Typical queries @subsection Reporting monthly expenses @findex --monthly @findex --display @var{EXPR} @findex --period-sort @var{VEXPR} @findex --related @findex --subtotal The following query makes it easy to see monthly expenses, with each month's expenses sorted by the amount: @smallexample @c command:validate $ ledger -M --period-sort "(amount)" reg ^expenses @end smallexample Now, you might wonder where the money came from to pay for these things. To see that report, add @option{--related (-r)}, which shows the ``related account'' postings: @smallexample @c command:validate $ ledger -M --period-sort "(amount)" -r reg ^expenses @end smallexample 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: @smallexample @c command:validate $ ledger -M -r --display 'account=~/mastercard/' reg ^expenses @end smallexample 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}. This works just as well for reporting the overall total, too: @smallexample @c command:validate $ ledger -s -r --display "account=~/mastercard/" reg ^expenses @end smallexample The @option{--subtotal (-s)} option subtotals all postings, just as @option{--monthly (-M)} subtotaled by the month. The running total in both cases is off, however, since a display expression is being used. @node Advanced Reports, , Typical queries, Building Reports @section Advanced Reports @menu * Asset Allocation:: * Visualizing with Gnuplot:: @end menu @node Asset Allocation, Visualizing with Gnuplot, Advanced Reports, Advanced Reports @subsection Asset Allocation A very popular method of managing portfolios is to control the percent allocation of assets by certain categories. The mix of categories and the weights applied to them vary by investing philosophy, but most follow a similar pattern. Tracking asset allocation in ledger is not difficult but does require some additional effort to describe how the various assets you own contribute to the asset classes you want to track. In our simple example we assume you want to apportion your assets into the general categories of domestic and international equities (stocks) and a combined category of bonds and cash. For illustrative purposes, we will use several publicly available mutual funds from Vanguard. The three funds we will track are the Vanguard 500 IDX FD Signal (VIFSX), the Vanguard Target Retirement 2030 (VTHRX), and the Vanguard Short Term Federal Fund (VSGBX). Each of these funds allocates assets to different categories of the investment universe and in different proportions. When you buy a share of VTHRX, that share is partially invested in equities, and partially invested in bonds and cash. Below is the asset allocation for each of the instruments listed above: @multitable @columnfractions .2 .2 .3 .3 @item @tab Domestic @tab Global @tab @item Symbol @tab Equity @tab Equity @tab bonds/cash @item VIFSX @tab 100% @tab @tab @item VTHRX @tab 24.0% @tab 56.3% @tab 19.7% @item VSGBX @tab @tab @tab 100% @end multitable These numbers are available from the prospectus of any publicly available mutual fund. Of course a single stock issue is 100% equity and a single bond issue is 100% bonds. We track purchases of specific investments using the symbol of that investment as its commodity. How do we tell Ledger that a share of VTHRX is 24% Domestic equity? Enter automatic transactions and virtual accounts. At the top of our ledger we enter automatic transactions that describe these proportions to Ledger. In the same entries we set up virtual accounts that let us separate these abstract calculations from our actual balances. For the three instruments listed above, those automatic transactions would look like: @smallexample @c input:582C8C2 = expr ( commodity == 'VIFSX' ) (Allocation:Equities:Domestic) 1.000 = expr ( commodity == 'VTHRX' ) (Allocation:Equities:Global) 0.240 (Allocation:Equities:Domestic) 0.563 (Allocation:Bonds/Cash) 0.197 = expr ( commodity == 'VBMFX') (Allocation:Bonds/Cash) 1.000 2015-01-01 Buy VIFSX Assets:Broker 100 VIFSX Assets:Cash $-10000 2015-01-01 Buy VTHRX Assets:Broker 10 VTHRX Assets:Cash $-10000 2015-01-01 Buy VBMFX Assets:Broker 1 VBMFX Assets:Cash $-10000 @end smallexample How do these work? First the @samp{=} sign at the beginning of the line tells ledger this is an automatic transaction to be applied when the condition following the @samp{=} is true. After the @samp{=} sign is a value expression (@pxref{Value Expressions}) that returns true any time a posting contains the commodity of interest. The following line gives the proportions (not percentages) of each unit of commodity that belongs to each asset class. Whenever Ledger sees a buy or sell of a particular commodity it will credit or debit these virtual accounts with that proportion of the number of shares moved. Now that Ledger understands how to distribute the commodities amongst the various asset classes how do we get a report that tells us our current allocation? Using the balance command and some tricky formatting! @smallexample @c command:582C8C2 ledger bal Allocation --current --format "\ %-17((depth_spacer)+(partial_account))\ %10(percent(market(display_total), market(parent.total)))\ %16(market(display_total))\n%/" @end smallexample Which yields: @smallexample @c output:582C8C2 Allocation 100.00% $30000 Bonds/Cash 39.90% $11970 Equities 60.10% $18030 Domestic 86.69% $15630 Global 13.31% $2400 @end smallexample Let's look at the Ledger invocation a bit closer. The command above is split into lines for clarity. The first line is very vanilla Ledger asking for the current balances of the account in the ``Allocation'' tree, using a special formatter. @cindex depth_spacer @cindex display_total @cindex parent.total The magic is in the formatter. The second line simply tells Ledger to print the partial account name indented by its depth in the tree. The third line is where we calculate and display the percentages. The @code{display_total} command gives the values of the total calculated for the account in this line. The @code{parent.total} command gives the total for the next level up in the tree. @code{percent} formats their ratio as a percentage. The fourth line tells ledger to display the current market value of the line. The last two characters @samp{%/} tell Ledger what to do for the last line, in this case, nothing. @node Visualizing with Gnuplot, , Asset Allocation, Advanced Reports @subsection Visualizing with Gnuplot @cindex plotting @cindex Gnuplot @findex --amount-data @findex --total-data @findex --limit @var{EXPR} @findex --display @var{EXPR} If you have the ``Gnuplot'' program installed, you can graph any of the above register reports. The script to do this is included in the ledger distribution, and is named @file{contrib/report}. Install @file{report} anywhere along your @env{PATH}, and then use @file{report} instead of @file{ledger} when doing a register report. The only thing to keep in mind is that you must specify @option{--amount-data (-j)} or @option{--total-data (-J)} to indicate whether ``Gnuplot'' should plot the amount, or the running total. For example, this command plots total monthly expenses made on your MasterCard. @smallexample $ report -j -M -r --display "account =~ /mastercard/" reg ^expenses @end smallexample The @file{report} script is a very simple Bourne shell script, that passes a set of scripted commands to ``Gnuplot''. Feel free to modify the script to your liking, since you may prefer histograms to line plots, for example. Here are some useful plots: @smallexample report -j -M reg ^expenses # monthly expenses report -J reg checking # checking account balance report -J reg ^income ^expenses # cash flow report # net worth report, ignoring non-$ postings report -J -l "Ua>=@{\$0.01@}" reg ^assets ^liab # net worth report starting last February. the use of a display # predicate (-d) is needed, otherwise the balance will start at # zero, and thus the y-axis will not reflect the true balance report -J -l "Ua>=@{\$0.01@}" -d "d>=[last feb]" reg ^assets ^liab @end smallexample The last report uses both a calculation predicate @option{--limit @var{EXPR} (-l)} and a display predicate @option{--display @var{EXPR} (-d)}. The calculation predicate limits the report to postings whose amount is greater than or equal to $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 February, even though those transactions from before will be computed as part of the balance. @node Reporting Commands, Command-Line Syntax, Building Reports, Top @chapter Reporting Commands @menu * Primary Financial Reports:: Reports in other formats:: Reports about * Reports in other Formats:: * Reports about your Journals:: @end menu @node Primary Financial Reports, Reports in other Formats, Reporting Commands, Reporting Commands @section Primary Financial Reports @menu * The @command{balance} command:: * The @command{equity} command:: * The @command{register} command:: * The @command{print} command:: @end menu @node The @command{balance} command, The @command{equity} command, Primary Financial Reports, Primary Financial Reports @subsection The @command{balance} command @findex balance The @command{balance} command reports the current balance of all accounts. It accepts a list of optional regexes, which confine the balance report to the matching accounts. If an account contains multiple types of commodities, each commodity's total is reported separately. @node The @command{equity} command, The @command{register} command, The @command{balance} command, Primary Financial Reports @subsection The @command{equity} command @findex equity The @command{equity} command prints out account balances as if they were transactions. This makes it easy to establish the starting balances for an account, such as when @ref{Archiving Previous Years}. @node The @command{register} command, The @command{print} command, The @command{equity} command, Primary Financial Reports @subsection The @command{register} command @findex register @findex --amount-data @findex --total-data The @command{register} command displays all the postings occurring in a single account, line by line. The account regex must be specified as the only argument to this command. If any regexes occur after the required account name, the register will contain only those postings that match, which makes it very useful for hunting down a particular posting. The output from @command{register} is very close to what a typical checkbook, or single-account ledger, would look like. It also shows a running balance. The final running balance of any register should always be the same as the current balance of that account. If you have ``Gnuplot'' installed, you may plot the amount or running total of any register by using the script @file{report}, which is included in the Ledger distribution. The only requirement is that you add either @option{--amount-data (-j)} or @option{--total-data (-J)} to your @command{register} command, in order to plot either the amount or total column, respectively. @node The @command{print} command, , The @command{register} command, Primary Financial Reports @subsection The @command{print} command @findex print The @command{print} command prints out ledger transactions in a textual format that can be parsed by Ledger. They will be properly formatted, and output in the most economic form possible. The @command{print} command also takes a list of optional regexes, which will cause only those postings which match in some way to be printed. The @command{print} command can be a handy way to clean up a ledger file whose formatting has gotten out of hand. @node Reports in other Formats, Reports about your Journals, Primary Financial Reports, Reporting Commands @section Reports in other Formats @menu * Comma Separated Values files:: * The @command{lisp} command:: * Emacs @command{org} Mode:: * Org mode with Babel:: * The @command{pricemap} command:: * The @command{xml} command:: * @command{prices} and @command{pricedb} commands:: @end menu @node Comma Separated Values files, The @command{lisp} command, Reports in other Formats, Reports in other Formats @subsection Comma Separated Values files @menu * The @command{csv} command:: * The @command{convert} command:: @end menu @node The @command{csv} command, The @command{convert} command, Comma Separated Values files, Comma Separated Values files @subsubsection The @command{csv} command @findex csv The @command{csv} command prints the desired ledger transactions in a csv format suitable for importing into other programs. You can specify the transactions to print using all the normal limiting and searching functions. @node The @command{convert} command, , The @command{csv} command, Comma Separated Values files @subsubsection The @command{convert} command @cindex csv importing @cindex comma separated variable file reading @findex convert @findex --input-date-format @var{DATE_FORMAT} The @command{convert} command parses a comma separated value (csv) file and prints Ledger transactions. Many banks offer csv file downloads. Unfortunately, the file formats, aside from the commas, are all different. The ledger @command{convert} command tries to help as much as it can. Your bank's csv files will have fields in different orders from other banks, so there must be a way to tell Ledger what to expect. Insert a line at the beginning of the csv file that describes the fields to Ledger. For example, this is a portion of a csv file downloaded from a credit union in the United States: @smallexample Account Name: VALUFIRST CHECKING Account Number: 71 Date Range: 11/13/2011 - 12/13/2011 Transaction Number,Date,Description,Memo,Amount Debit,Amount Credit,Balance,Check Number,Fees 767718,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-8.80,,00001640.04,, 767406,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-1.03,,00001648.84,, 683342,12/13/2011,"Visa Checking","NetFlix Date 12/12/11 000326585896 5968",-21.85,,00001649.87,, 639668,12/13/2011,"Withdrawal","ID: 1741472662 CO: XXAA.COM PAYMNT",-236.65,,00001671.72,, 1113648,12/12/2011,"Withdrawal","Tuscan IT #00037657",-29.73,,00001908.37,, @end smallexample Unfortunately, as it stands Ledger cannot read it, but you can. Ledger expects the first line to contain a description of the fields on each line of the file. The fields ledger can recognize contain these case-insensitive strings @code{date}, @code{posted}, @code{code}, @code{payee} or @code{desc} or @code{description}, @code{amount}, @code{cost}, @code{total}, and @code{note}. Delete the account description lines at the top, and replace the first line in the data above with: @smallexample ,date,payee,note,amount,,,code, @end smallexample Then execute ledger like this: @smallexample $ ledger convert download.csv --input-date-format "%m/%d/%Y" @end smallexample Where the @option{--input-date-format @var{DATE_FORMAT}} option tells ledger how to interpret the dates. Importing csv files is a lot of work, but is very amenable to scripting. 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 @samp{; transid: 767718} in the first transaction from the file above. @findex --invert @findex --auto-match @findex --account @var{STR} @findex --rich-data The @command{convert} command accepts four options. They are @option{--invert} which inverts the amount field, @option{--auto-match} which automatically matches an account from the Ledger journal for every CSV line, @option{--account @var{STR}} which you can use to specify the account to balance against, and @option{--rich-data} which stores additional tag/value pairs. Using the two first lines of the above csv file, @smallexample @c file:01B0350 ,date,payee,note,amount,,,code, 767718,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-8.80,,00001640.04,, 767406,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-1.03,,00001648.84,, @end smallexample and launching the below command, @smallexample @c command:01B0350,with_file:download.csv $ ledger convert download.csv --input-date-format "%m/%d/%Y" \ --invert --account Assets:MyBank --rich-data \ --file sample.dat --now=2012/01/13 @end smallexample you will get the result: @smallexample @c output:01B0350 2011/12/13 * Withdrawal ;ACE HARDWARE 16335 S HOUGHTON RD ; CSV: 767718,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-8.80,,00001640.04,, ; Imported: 2012/01/13 ; UUID: dfdc3c3d5c54c6967dd39d5b4e4fd1ea76e87233 Expenses:Unknown 8.8 Assets:MyBank 2011/12/13 * Withdrawal ;ACE HARDWARE 16335 S HOUGHTON RD ; CSV: 767406,12/13/2011,"Withdrawal","ACE HARDWARE 16335 S HOUGHTON RD",-1.03,,00001648.84,, ; Imported: 2012/01/13 ; UUID: 63086448b1f29f7fd6efb11ea40660185a213f9d Expenses:Unknown 1.03 Assets:MyBank @end smallexample The three added metadata are: @samp{CSV} as the original line from csv file, @samp{Imported} as the date when the csv file was imported into Ledger, and @samp{UUID} as a checksum of original csv line. If an entry with the same @samp{UUID} tag is already included in the normal ledger file (specified via @option{--file @var{FILE} (-f)} or via the environment variable @env{LEDGER_FILE}) this entry will not be printed again. In the output above, the account is @samp{Expenses:Unknown} for CSV lines. You can use the @option{--auto-match} option to automatically match an account from your Ledger journal. You can also use @command{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 @c input:validate 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 @samp{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 @command{lisp} command, Emacs @command{org} Mode, Comma Separated Values files, Reports in other Formats @subsection The @command{lisp} command @findex lisp @findex emacs The @command{lisp} command prints results in a form that can be read directly by Emacs Lisp. The format of the @code{sexp} is: @smallexample ((BEG-POS CLEARED DATE CODE PAYEE (ACCOUNT AMOUNT)...) ; list of postings ...) ; list of transactions @end smallexample @noindent @command{emacs} can also be used as a synonym for @command{lisp}. @node Emacs @command{org} Mode, Org mode with Babel, The @command{lisp} command, Reports in other Formats @subsection Emacs @command{org} Mode @findex org The @command{org} command produces a journal file suitable for use in the 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 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 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 application of the Babel system: @smallexample * A simple test of ledger in an org file The following are some entries and I have requested that ledger be run to generate a balance on the accounts. I could have asked for a register or, in fact, anything at all the ledger can do through command-line options. #+begin_src ledger :cmdline bal :results value 2010/01/01 * Starting balance assets:bank:savings £1300.00 income:starting balances 2010/07/22 * Got paid assets:bank:chequing £1000.00 income:salary 2010/07/23 Rent expenses:rent £500.00 assets:bank:chequing #+end_src #+results: : £1800.00 assets:bank : £500.00 chequing : £1300.00 savings : £500.00 expenses:rent : £-2300.00 income : £-1000.00 salary : £-1300.00 starting balances @end smallexample Typing @kbd{C-c C-c} anywhere in the ``ledger source code block'' will invoke ledger on the contents of that block and generate a ``results'' block. The results block can appear anywhere in the file but, by default, will appear immediately below the source code block. You can combine multiple source code blocks before executing ledger and do all kinds of other wonderful things with Babel (and Org mode). @node Org mode with Babel, The @command{pricemap} command, Emacs @command{org} Mode, Reports in other Formats @subsection Org mode with Babel Using Babel, it is possible to record financial transactions conveniently in an org file and subsequently generate the financial reports required. As of Org mode 7.01, Ledger support is provided. Check the Babel documentation on Worg for instructions on how to achieve this but I currently do this directly as follows: @smallexample (org-babel-do-load-languages 'org-babel-load-languages '((ledger . t) ;this is the important one for this tutorial )) @end smallexample Once Ledger support in Babel has been enabled, we can proceed to include Ledger entries within an org file. There are three ways (at least) in which these can be included: @enumerate @item place all Ledger entries within one single source block and execute this block with different arguments to generate the appropriate reports, @item place Ledger entries in more than one source block and use the @code{noweb} literary programming approach, supported by Babel, to combine these into one block elsewhere in the file for processing by Ledger, @item place Ledger entries in different source blocks and use @code{tangle} to generate a Ledger file which you can subsequently process using Ledger directly. @end enumerate The first two are described in more detail in this short tutorial. @menu * Embedded Ledger example with single source block:: * Multiple Ledger source blocks with @code{noweb}:: * Income Entries:: * Expenses:: * Financial Summaries:: * An overall balance summary:: * Generating a monthly register:: * Summary:: @end menu @node Embedded Ledger example with single source block, Multiple Ledger source blocks with @code{noweb}, Org mode with Babel, Org mode with Babel @subsubsection Embedded Ledger example with single source block The easiest, albeit possibly least 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 #+name: allinone #+begin_src ledger 2010/01/01 * Starting balance assets:bank:savings £1300.00 income:starting balances 2010/07/22 * Got paid assets:bank:chequing £1000.00 income:salary 2010/07/23 Rent expenses:rent £500.00 assets:bank:chequing 2010/07/24 Food expenses:food £150.00 assets:bank:chequing 2010/07/31 * Interest on bank savings assets:bank:savings £3.53 income:interest 2010/07/31 * Transfer savings assets:bank:savings £250.00 assets:bank:chequing 2010/08/01 got paid again assets:bank:chequing £1000.00 income:salary #+end_src @end smallexample In this example, we have combined both expenses and income into one set of Ledger entries. We can now generate register and balance reports (as well as many other types of reports) using Babel to invoke Ledger with specific arguments. The arguments are passed to Ledger using the @code{:cmdline} header argument. In the code block above, there is no such argument so the system takes the default. For Ledger code blocks, the default @code{:cmdline} argument is @code{bal} and the result of evaluating this code block (@kbd{C-c C-c}) would be: @smallexample #+results: allinone() : £2653.53 assets:bank : £1100.00 chequing : £1553.53 savings : £650.00 expenses : £150.00 food : £500.00 rent : £-3303.53 income : £-3.53 interest : £-2000.00 salary : £-1300.00 starting balances @end smallexample If, instead, you wished to generate a register of all the transactions, you would change the @code{#+begin_src} line for the code block to include the required command-line option: @smallexample #+begin_src ledger :cmdline reg @end smallexample 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 @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 @code{noweb} features of Babel, to help us. @node Multiple Ledger source blocks with @code{noweb}, Income Entries, Embedded Ledger example with single source block, Org mode with Babel @subsubsection Multiple Ledger source blocks with @code{noweb} The @code{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: @node Income Entries, Expenses, Multiple Ledger source blocks with @code{noweb}, Org mode with Babel @subsubsection Income Entries The first set of entries relates to income, either monthly pay or interest, all typically going into one of my bank accounts. Here, I have placed several entries, but we could have had each entry in a separate @code{src} block. Note that all code blocks you wish to refer to later must have the @code{:noweb yes} header argument specified. @smallexample #+name: income #+begin_src ledger :noweb yes 2010/01/01 * Starting balance assets:bank:savings £1300.00 income:starting balances 2010/07/22 * Got paid assets:bank:chequing £1000.00 income:salary 2010/07/31 * Interest on bank savings assets:bank:savings £3.53 income:interest 2010/07/31 * Transfer savings assets:bank:savings £250.00 assets:bank:chequing 2010/08/01 got paid again assets:bank:chequing £1000.00 income:salary #+end_src @end smallexample @node Expenses, Financial Summaries, Income Entries, Org mode with Babel @subsubsection Expenses The following entries relate to personal expenses, such as rent and food. Again, these have all been placed in a single @code{src} block but could have been done individually. @smallexample #+name: expenses #+begin_src ledger :noweb yes 2010/07/23 Rent expenses:rent £500.00 assets:bank:chequing 2010/07/24 Food expenses:food £150.00 assets:bank:chequing #+end_src @end smallexample @node Financial Summaries, An overall balance summary, Expenses, Org mode with Babel @subsubsection Financial Summaries Given the ledger entries defined above in the income and expenses code blocks, we can now refer to these using the noweb expansion directives, @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. @node An overall balance summary, Generating a monthly register, Financial Summaries, Org mode with Babel @subsubsection An overall balance summary @findex --subtotal The overall balance of your account and expenditure with a breakdown according to category is specified by passing the @code{:cmdline bal} argument to Ledger. This code block can now be evaluated (@kbd{C-c C-c}) and the results generated by incorporating the transactions referred to by the @code{<<income>>} and @code{<<expenses>>} lines. @smallexample #+name: balance #+begin_src ledger :cmdline bal :noweb yes <<income>> <<expenses>> #+end_src #+results: balance : £2653.53 assets:bank : £1100.00 chequing : £1553.53 savings : £650.00 expenses : £150.00 food : £500.00 rent : £-3303.53 income : £-3.53 interest : £-2000.00 salary : £-1300.00 starting balances @end smallexample If you want a less detailed breakdown of where your money is, you can specify the @option{--collapse (-n)} flag (i.e. @samp{:cmdline -n bal}) to tell Ledger to exclude sub-accounts in the report. @smallexample #+begin_src ledger :cmdline -n bal :noweb yes <<income>> <<expenses>> #+end_src #+results: : £2653.53 assets : £650.00 expenses : £-3303.53 income @end smallexample @node Generating a monthly register, Summary, An overall balance summary, Org mode with Babel @subsubsection Generating a monthly register @findex register @findex --monthly You can also generate a monthly register (the @command{reg} command) by executing the following @code{src} block. This presents a summary of transactions for each monthly period (the @option{--monthly (-M)} argument) with a running total in the final column (which should be 0 at the end if all the entries are correct). @smallexample #+name: monthlyregister #+begin_src ledger :cmdline -M reg :noweb yes <<income>> <<expenses>> #+end_src #+results: monthlyregister :2010/01/01 - 2010/01/31 assets:bank:savings £1300.00 £1300.00 : in:starting balances £-1300.00 0 :2010/07/01 - 2010/07/31 assets:bank:chequing £100.00 £100.00 : assets:bank:savings £253.53 £353.53 : expenses:food £150.00 £503.53 : expenses:rent £500.00 £1003.53 : income:interest £-3.53 £1000.00 : income:salary £-1000.00 0 :2010/08/01 - 2010/08/01 assets:bank:chequing £1000.00 £1000.00 : income:salary £-1000.00 0 @end smallexample We could also generate a monthly report on our assets showing how these are increasing (or decreasing!). In this case, the final column will be the running total of the assets in our ledger. @smallexample #+name: monthlyassetsregister #+begin_src ledger :cmdline -M reg assets :noweb yes <<income>> <<expenses>> #+end_src #+results: monthlyassetsregister : 2010/01/01 - 2010/01/31 assets:bank:savings £1300.00 £1300.00 : 2010/07/01 - 2010/07/31 assets:bank:chequing £100.00 £1400.00 : assets:bank:savings £253.53 £1653.53 : 2010/08/01 - 2010/08/01 assets:bank:chequing £1000.00 £2653.53 @end smallexample @node Summary, , Generating a monthly register, Org mode with Babel @subsubsection Summary This short tutorial shows how Ledger entries can be embedded in an org 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 on a ledger. @node The @command{pricemap} command, The @command{xml} command, Org mode with Babel, Reports in other Formats @subsection The @command{pricemap} command @findex pricemap If you have the @file{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 multiple investments valued in those currencies. @node The @command{xml} command, @command{prices} and @command{pricedb} commands, The @command{pricemap} command, Reports in other Formats @subsection The @command{xml} command @findex xml By default, Ledger uses a human-readable data format, and displays its reports in a manner meant to be read on screen. For the purpose of writing tools which use Ledger, however, it is possible to read and display data using XML. This section documents that format. The general format used for Ledger data is: @smallexample <?xml version="1.0"?> <ledger> <xact>...</xact> <xact>...</xact> <xact>...</xact>... </ledger> @end smallexample The data stream is enclosed in a @code{ledger} tag, which contains a series of one or more transactions. Each @code{xact} describes one transaction and contains a series of one or more postings: @smallexample <xact> <en:date>2004/03/01</en:date> <en:cleared/> <en:code>100</en:code> <en:payee>John Wiegley</en:payee> <en:postings> <posting>...</posting> <posting>...</posting> <posting>...</posting>... </en:postings> </xact> @end smallexample 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 @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}. Within the @code{en:postings} tag is a series of one or more @code{posting}'s, which have the following form: @smallexample <posting> <tr:account>Expenses:Computer:Hardware</tr:account> <tr:amount> <value type="amount"> <amount> <commodity flags="PT">$</commodity> <quantity>90.00</quantity> </amount> </value> </tr:amount> </posting> @end smallexample This is a basic posting. It may also begin with @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 @code{tr:amount}. Within this tag is a @code{value} tag, of which there are four different kinds, each with its own format: @enumerate @item Boolean, @item integer, @item amount, @item balance. @end enumerate The format of a Boolean value is @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 @code{integer} tag, for example: @smallexample <integer>12036</integer> @end smallexample The format of an amount contains two members, the commodity and the quantity. The commodity can have a set of flags that indicate how to display it. The meaning of the flags (all of which are optional) are: @table @code @item P The commodity is prefixed to the value. @item S The commodity is separated from the value by a space. @item T Thousands markers are used to display the amount. @item E The format of the amount is European, with period used as a thousands marker, and comma used as the decimal point. @end table The actual quantity for an amount is an integer of arbitrary size. Ledger uses the GNU multiple precision arithmetic library to handle such values. The XML format assumes the reader to be equally capable. Here is an example amount: @smallexample <value type="amount"> <amount> <commodity flags="PT">$</commodity> <quantity>90.00</quantity> </amount> </value> @end smallexample Lastly, a balance value contains a series of amounts, each with a different commodity. Unlike the name, such a value does need to balance. It is called a balance because it sums several amounts. For example: @smallexample <value type="balance"> <balance> <amount> <commodity flags="PT">$</commodity> <quantity>90.00</quantity> </amount> <amount> <commodity flags="TE">DM</commodity> <quantity>200.00</quantity> </amount> </balance> </value> @end smallexample That is the extent of the XML data format used by Ledger. It will output such data if the @command{xml} command is used, and can read the same data. @node @command{prices} and @command{pricedb} commands, , The @command{xml} command, Reports in other Formats @subsection @command{prices} and @command{pricedb} commands @findex prices @findex pricedb @findex --average The @command{prices} command displays the price history for matching commodities. The @option{--average (-A)} option is useful with this report, to display the running average price, or @option{--deviation (-D)} to show each price's deviation from that average. There is also a @command{pricedb} command which outputs the same information as @command{prices}, but does so in a format that can be parsed by Ledger. This is useful for generating and tidying up pricedb database files. @node Reports about your Journals, , Reports in other Formats, Reporting Commands @section Reports about your Journals @findex --count @menu * @command{accounts}:: * @command{payees}:: * @command{commodities}:: * @command{tags}:: * @command{xact}:: * @command{stats}:: * @command{select}:: @end menu @node @command{accounts}, @command{payees}, Reports about your Journals, Reports about your Journals @subsection @command{accounts} @findex accounts The @command{accounts} command reports all of the accounts in the journal. Following the command with a regular expression will limit the output to accounts matching the regex. The output is sorted by name. Using the @option{--count} option will tell you how many entries use each account. @node @command{payees}, @command{commodities}, @command{accounts}, Reports about your Journals @subsection @command{payees} @findex payees The @command{payees} command reports all of the unique payees in the journal. Using the @option{--count} option will tell you how many entries use each payee. To filter the payees displayed you must use the prefix @@: @smallexample @c command:validate $ ledger payees @@Nic @end smallexample @smallexample Nicolas Nicolas BOILABUS Oudtshoorn Municipality Vaca Veronica @end smallexample @node @command{commodities}, @command{tags}, @command{payees}, Reports about your Journals @subsection @command{commodities} @findex commodities Report all commodities present in the journals under consideration. The output is sorted by name. Using the @option{--count} option will tell you how many entries use each commodity. @node @command{tags}, @command{xact}, @command{commodities}, Reports about your Journals @subsection @command{tags} @findex tags @findex --values The @command{tags} command reports all of the tags in the journal. The output is sorted by name. Using the @option{--count} option will tell you how many entries use each tag. Using the @option{--values} option will report the values used by each tag. @node @command{xact}, @command{stats}, @command{tags}, Reports about your Journals @subsection @command{xact} @findex draft @findex entry @findex xact The @command{xact} command simplifies 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: @smallexample @c input:03ACB97 2004/03/15 * Viva Italiano Expenses:Food $12.45 Expenses:Tips $2.55 Liabilities:MasterCard $-15.00 @end smallexample Now it's @samp{2004/4/9}, and you've just eaten at @samp{Viva Italiano} again. The exact amounts are different, but the overall form is the same. With the @command{xact} command you can type: @smallexample @c command:03ACB97 $ ledger xact 2004/4/9 viva food 11 tips 2.50 @end smallexample This produces the following output: @smallexample @c output:03ACB97 2004/04/09 Viva Italiano Expenses:Food $11.00 Expenses:Tips $2.50 Liabilities:MasterCard @end smallexample It works by finding a past posting matching the regular expression @samp{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}. Here are a few more examples of the @command{xact} command, assuming the above journal transaction: @smallexample $ ledger xact 4/9 viva 11.50 $ ledger xact 4/9 viva 11.50 checking # (from `checking') $ ledger xact 4/9 viva food 11.50 tips 8 $ ledger xact 4/9 viva food 11.50 tips 8 cash $ ledger xact 4/9 viva food $11.50 tips $8 cash $ ledger xact 4/9 viva dining "DM 11.50" @end smallexample @command{draft} and @command{entry} are both synonyms of @command{xact}. @command{entry} is provided for backwards compatibility with Ledger 2.X. @node @command{stats}, @command{select}, @command{xact}, Reports about your Journals @subsection @command{stats} @findex stats @findex stat @value{FIXME:UNDOCUMENTED} @node @command{select}, , @command{stats}, Reports about your Journals @subsection @command{select} @findex select @value{FIXME:UNDOCUMENTED} @node Command-Line Syntax, Budgeting and Forecasting, Reporting Commands, Top @chapter Command-Line Syntax @menu * Basic Usage:: * Command-Line Quick Reference:: * Detailed Option Description:: * Period Expressions:: @end menu @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 survey this to get an overview before diving into the @ref{Ledger Tutorial} and more detailed examples that follow. Ledger has a very simple command-line interface, named---enticingly enough---@file{ledger}. It supports a few reporting commands, and a large number of options for refining the output from those commands. The basic syntax of any ledger command is: @smallexample $ ledger [OPTIONS...] COMMAND [ARGS...] @end smallexample After the command word there may appear any number of arguments. For most commands, these arguments are regular expressions that cause the output to relate only to postings matching those regular expressions. For the @command{xact} command, the arguments have a special 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 @samp{@@}. For example, the following balance command reports account totals for rent, food and movies, but only those whose payee matches Freddie: @smallexample @c command:validate $ ledger bal rent food movies payee freddie @end smallexample @noindent or @smallexample @c command:validate $ ledger bal rent food movies @@freddie @end smallexample There are many, many command options available with the @file{ledger} program, and it takes a while to master them. However, none of them are required to use the basic reporting commands. @node Command-Line Quick Reference, Detailed Option Description, Basic Usage, Command-Line Syntax @section Command-Line Quick Reference @menu * Basic Reporting Commands:: * Basic Options:: * Report Filtering:: * Error Checking and Calculation Options:: * Output Customization:: * Grouping Options:: * Commodity Reporting:: @end menu @node Basic Reporting Commands, Basic Options, Command-Line Quick Reference, Command-Line Quick Reference @subsection Basic Reporting Commands @ftable @command @item balance @itemx bal Show account balances. @item register @itemx reg Show all transactions with running total. @item csv @cindex csv exporting Show transactions in csv format, for exporting to other programs. @item print Print transactions in a format readable by ledger. @item xml Produce XML output of the register command. @item lisp @itemx emacs Produce s-expression output, suitable for Emacs. @item equity Print account balances as transactions. @item prices Print price history for matching commodities. @item pricedb Print price history for matching commodities in a format readable by ledger. @item xact Generate transactions based on previous postings. @end ftable @node Basic Options, Report Filtering, Basic Reporting Commands, Command-Line Quick Reference @subsection Basic Options @ftable @option @item --help @itemx -h Display the man page for @file{ledger}. @item --version Print version information and exit. @item --file @var{FILE} @itemx -f @var{FILE} Read @file{FILE} as a ledger file. @item --output @var{FILE} @itemx -o @var{FILE} Redirect output to @file{FILE}. @item --init-file @var{FILE} @itemx -i @var{FILE} Specify an options file. @item --import @var{FILE} Import @var{FILE} as Python module. @item --account @var{STR} @itemx -a @var{STR} Specify default account @var{STR} for QIF file postings. @end ftable @node Report Filtering, Error Checking and Calculation Options, Basic Options, Command-Line Quick Reference @subsection Report Filtering @ftable @option @item --current @itemx -c Display only transactions on or before the current date. @item --begin @var{DATE} @itemx -b @var{DATE} Limit the processing to transactions on or after @var{DATE}. @item --end @var{DATE} @itemx -e @var{DATE} Limit the processing to transactions before @var{DATE}. @item --period @var{PERIOD_EXPRESSION} @itemx -p @var{PERIOD_EXPRESSION} Limit the processing to transactions in @var{PERIOD_EXPRESSION}. @item --period-sort @var{VEXPR} Sort postings within each period according to @var{VEXPR}. @item --cleared @itemx -C Display only cleared postings. @item --dc Display register or balance in debit/credit format. @item --uncleared @itemx -U Display only uncleared postings. @item --real @itemx -R Display only real postings. @item --actual @itemx -L Display only actual postings, not automated ones. @item --related @itemx -r Display related postings. @item --budget Display how close your postings meet your budget. @item --add-budget Show unbudgeted postings. @item --unbudgeted Show only unbudgeted postings. @item --forecast-while @var{VEXPR} @itemx --forecast @var{VEXPR} Project balances into the future. @item --limit @var{EXPR} @itemx -l @var{EXPR} Limit which postings are used in calculations by @var{EXPR}. @item --amount @var{EXPR} @itemx -t @var{EXPR} Change value expression reported in @command{register} report. @item --total @var{VEXPR} @itemx -T @var{VEXPR} Change the value expression used for ``totals'' column in @command{register} and @command{balance} reports. @end ftable @node Error Checking and Calculation Options, Output Customization, Report Filtering, Command-Line Quick Reference @subsection Error Checking and Calculation Options @ftable @option @item --strict Accounts, tags or commodities not previously declared will cause warnings. @item --pedantic Accounts, tags or commodities not previously declared will cause errors. @item --check-payees Enable strict and pedantic checking for payees as well as accounts, commodities and tags. This only works in conjunction with @option{--strict} or @option{--pedantic}. @item --immediate Instruct ledger to evaluate calculations immediately rather than lazily. @end ftable @node Output Customization, Grouping Options, Error Checking and Calculation Options, Command-Line Quick Reference @subsection Output Customization @ftable @option @item --collapse @itemx -n Collapse transactions with multiple postings. @item --subtotal @itemx -s Report register as a single subtotal. @item --by-payee @itemx -P Report subtotals by payee. @item --empty @itemx -E Include empty accounts in the report. @item --weekly @itemx -W Report posting totals by week. @item --quarterly Report posting totals by quarter. @item --yearly @itemx -Y Report posting totals by year. @item --dow Report posting totals by day of week. @item --sort @var{VEXPR} @itemx -S @var{VEXPR} Sort a report using @var{VEXPR}. @item --wide @itemx -w Assume 132 columns instead of 80. @item --head @var{INT} Report the first @var{INT} postings. @item --tail @var{INT} Report the last @var{INT} postings. @item --pager @var{FILE} Direct output to @var{FILE} pager program. @item --no-pager Direct output to stdout, avoiding pager program. @item --average @itemx -A Report the average posting value. @item --deviation @itemx -D Report each posting's deviation from the average. @item --percent @itemx -% Show subtotals in the balance report as percentages. @c @item --totals @c Include running total in the @command{xml} report @item --pivot @var{TAG} Produce a pivot table of the @var{TAG} type specified. @item --amount-data @itemx -j Show only the date and value columns to format the output for plots. @item --plot-amount-format @var{FORMAT_STRING} Specify the format for the plot output. @item --total-data @itemx -J Show only the date and total columns to format the output for plots. @item --plot-total-format @var{FORMAT_STRING} Specify the format for the plot output. @item --display @var{EXPR} @itemx -d @var{EXPR} Display only postings that meet the criteria in the @var{EXPR}. @item --date-format @var{DATE_FORMAT} @itemx -y @var{DATE_FORMAT} Change the basic date format used in reports. @item --format @var{FORMAT_STRING} @itemx --balance-format @var{FORMAT_STRING} @itemx --register-format @var{FORMAT_STRING} @itemx --prices-format @var{FORMAT_STRING} @itemx -F @var{FORMAT_STRING} Set the reporting format for various reports. @item --anon Print the ledger register with anonymized accounts and payees, useful for filing bug reports. @end ftable @node Grouping Options, Commodity Reporting, Output Customization, Command-Line Quick Reference @subsection Grouping Options @ftable @option @item --by-payee @itemx -P Group postings by common payee names. @item --daily @itemx -D Group postings by day. @item --weekly @itemx -W Group postings by week. @item --monthly @itemx -M Group postings by month. @item --quarterly Group postings by quarter. @item --yearly @itemx -Y Group postings by year. @item --dow Group by day of weeks. @item --subtotal @itemx -s Group postings together, similar to the balance report. @end ftable @node Commodity Reporting, , Grouping Options, Command-Line Quick Reference @subsection Commodity Reporting @ftable @option @item --price-db @var{FILE} Use @file{FILE} for retrieving stored commodity prices. @item --price-exp @var{INT} @itemx --leeway @var{INT} @itemx -Z @var{INT} Set expected freshness of prices in @var{INT} minutes. @item --download @itemx -Q Download quotes using the script named @file{getquote}. @c FIXME: The option doesn't exist currently. @c @item --getquote @var{FILE} @c Sets the path to a user-defined script to download commodity prices. @item --quantity @itemx -O Report commodity totals without conversion. @item --basis @itemx -B Report cost basis. @item --market @itemx -V Report last known market value. @item --gain @itemx -G Report net gain or loss for commodities that have a price history. @end ftable @node Detailed Option Description, Period Expressions, Command-Line Quick Reference, Command-Line Syntax @section Detailed Option Description @menu * Global Options:: * Session Options:: * Report Options:: * Basic options:: * Report filtering:: * Output customization:: * Commodity reporting:: * Environment variables:: @end menu @node Global Options, Session Options, Detailed Option Description, Detailed Option Description @subsection Global Options Options for Ledger reports affect three separate scopes of operation: Global, Session, and Report. In practice there is very little 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. @ftable @option @item --args-only Ignore all environment and init-file settings and use only command-line arguments to control Ledger. Useful for debugging or testing small journal files not associated with your main financial database. @item --debug @var{CODE} @value{FIXME:UNDOCUMENTED} If ledger has been built with debug options this will provide extra data during the run. @item --help @itemx -h Display the man page for @file{ledger}. @item --init-file @var{FILE} Specify the location of the init file. The default is home directory @file{~/.ledgerrc}, or current directory @file{./.ledgerrc} if not found in home directory. @item --options Display the options in effect for this Ledger invocation, along with their values and the source of those values, for example: @smallexample @c command:A9349E4,with_input:03ACB97 $ ledger --options bal --cleared @end smallexample @smallexample @c output:A9349E4 =============================================================================== [Global scope options] --args-only --args-only [Session scope options] --file = A9349E4.dat --file [Report scope options] --cleared --cleared --columns = 80 --columns --limit = cleared --cleared =============================================================================== $15.00 Expenses $12.45 Food $2.55 Tips $-15.00 Liabilities:MasterCard -------------------- 0 @end smallexample @noindent For the source column, a value starting with a @samp{-} or @samp{--} indicated the source was a command-line argument. If the entry starts with a @samp{$}, 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}. @item --script @var{FILE} Execute a ledger script. @item --trace @var{INT} Enable tracing. The @var{INT} specifies the level of trace desired. @item --verbose @itemx -v Print detailed information on the execution of Ledger. @item --verify Enable additional assertions during run-time. This causes a significant slowdown. When combined with @option{--debug @var{CODE}} ledger will produce memory trace information. @item --verify-memory Verify that every constructed object is properly destructed. This is for debugging purposes only. @item --version Print version information and exit. @end ftable @node Session Options, Report Options, Global Options, Detailed Option Description @subsection Session Options Options for Ledger reports affect three separate scopes of operation: Global, Session, and Report. In practice there is very little 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. @ftable @option @item --check-payees Enable strict and pedantic checking for payees as well as accounts, commodities and tags. This only works in conjunction with @option{--strict} or @option{--pedantic}. @item --day-break Break up @command{register} report of @ref{timelog} entries that span multiple days by day. @c see test/baseline/opt-day-break.dat @c @smallexample @c input: @c i 2015/ @c @end smallexample @c @smallexample @c command: @c $ ledger reg --day-break @c @end smallexample @c @smallexample @c output: @c @end smallexample @item --decimal-comma Direct Ledger to parse journals using the European standard comma as a decimal separator, not the usual period. @item --download @itemx -Q Direct Ledger to download prices. @c using the script defined via the option @c @option{--getquote @var{FILE}}. @item --explicit Direct Ledger to require pre-declarations for entities (such as accounts, commodities and tags) rather than taking entities from cleared transactions as defined. This option is useful in combination with @option{--strict} or @option{--pedantic}. @item --file @var{FILE} @itemx -f @var{FILE} Specify the input @file{FILE} for this invocation. @c FIXME: The option doesn't exist currently. @c @item --getquote @var{FILE} @c @cindex getquote @c @cindex download prices @c Tell ledger where to find the user defined script to download prices @c information. @item --input-date-format @var{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 Would convert the @file{Export.csv} file to ledger format, assuming the dates in the CSV file are like 12/23/2009 (@pxref{Date and Time Format Codes}). @item --master-account @var{STR} Prepend all account names with the argument. @smallexample @c command:A76BB56 $ ledger -f drewr3.dat bal --no-total --master-account HUMBUG @end smallexample @smallexample @c output:A76BB56 0 HUMBUG $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business $ -5,200.00 Savings $ -1,000.00 Equity:Opening Balances $ 6,654.00 Expenses $ 5,500.00 Auto $ 20.00 Books $ 300.00 Escrow $ 334.00 Food:Groceries $ 500.00 Interest:Mortgage $ -2,030.00 Income $ -2,000.00 Salary $ -30.00 Sales $ 180.00 Liabilities $ -20.00 MasterCard $ 200.00 Mortgage:Principal @end smallexample @item --no-aliases Ledger does not expand any aliases if this option is specified. @item --pedantic Accounts, tags or commodities not previously declared will cause errors. @item --permissive Quiet balance assertions. @item --price-db @var{FILE} Specify the location of the price entry data file. @item --price-exp @var{INT} @itemx --leeway @var{INT} @itemx -Z @var{INT} Set the expected freshness of price quotes, in @var{INT} 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. @item --strict Ledger normally silently accepts any account or commodity in a posting, even if you have misspelled a commonly used one. The option @option{--strict} changes that behavior. While running with @option{--strict}, Ledger interprets all cleared transactions as correct, and if it encounters 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 --recursive-aliases Normally, ledger only expands aliases once. With this option, ledger tries to expand the result of alias expansion recursively, until no more expansions apply. @item --time-colon The @option{--time-colon} option will display the value for a seconds based commodity as real hours and minutes. For example 8100 seconds by default will be displayed as 2.25 whereas with the @option{--time-colon} option they will be displayed as 2:15. @item --value-expr @var{VEXPR} Set a global value expression annotation. @c needs example @end ftable @node Report Options, Basic options, Session Options, Detailed Option Description @subsection Report Options Options for Ledger reports affect three separate scopes of operation: Global, Session, and Report. In practice there is very little 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. @ftable @option @item --abbrev-len @var{INT} Set the minimum length an account can be abbreviated to if it doesn't fit inside the @code{account-width}. If @var{INT} is zero, then the account name will be truncated on the right. If @var{INT} 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. @item --account @var{STR} Prepend @var{STR} to all accounts reported. That is, the option @samp{--account Personal} would tack @samp{Personal:} to the beginning of every account reported in a balance report or register report. @item --account-width @var{INT} Set the width of the account column in the @command{register} report to @var{INT} characters. @item --actual @itemx -L Report only real transactions, ignoring all automated or virtual transactions. @item --add-budget Show only unbudgeted postings. @item --amount @var{EXPR} @itemx -t @var{EXPR} Apply the given value expression to the posting amount (@pxref{Value Expressions}). Using @option{--amount @var{EXPR}} you can apply an arbitrary transformation to the postings. @item --amount-data @itemx -j On a register report print only the date and amount of postings. Useful for graphing and spreadsheet applications. @item --amount-width @var{INT} Set the width in characters of the amount column in the @command{register} report. @item --anon Anonymize registry output, mostly for sending in bug reports. @item --auto-match When generating a ledger transaction from a CSV file using the @command{convert} command, automatically match an account from the Ledger journal. @item --aux-date @itemx --effective Show auxiliary dates for all calculations (@pxref{Effective Dates}). @item --average @itemx -A Print average values over the number of transactions instead of running totals. @item --balance-format @var{FORMAT_STRING} Specify the format to use for the @command{balance} report (@pxref{Format Strings}). The default is: @smallexample "%(justify(scrub(display_total), 20, -1, true, color))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" "%$1\n%/" "--------------------\n" @end smallexample @item --base Reduce convertible commodities down the bottom of the conversion, e.g. display time in seconds. This also applies to custom commodity conversions (@pxref{Commodity equivalences}). @item --basis @itemx -B @itemx --cost Report the cost basis on all posting. @item --begin @var{DATE} Specify the start @var{DATE} of all calculations. Transactions before that date will be ignored. @item --bold-if @var{VEXPR} Print the entire line in bold if the given value expression is true (@pxref{Value Expressions}). @smallexample @c command:validate $ ledger reg Expenses --begin Dec --bold-if "amount>100" @end smallexample @noindent list all transactions since the beginning of December and print in bold any posting greater than $100. @item --budget Only display budgeted items. In a register report this displays transactions in the budget, in a balance report this displays accounts in the budget (@pxref{Budgeting and Forecasting}). @item --budget-format @var{FORMAT_STRING} Specify the format to use for the @command{budget} report (@pxref{Format Strings}). The default is: @smallexample "%(justify(scrub(display_total), 20, -1, true, color))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" "%$1\n%/" "--------------------\n" @end smallexample @item --by-payee @itemx -P Group the register report by payee. @item --cleared @itemx -C Consider only transactions that have been cleared for display and calculation. @item --cleared-format @var{FORMAT_STRING} @c FIXME thdox: to keep? Specify the format to use for the @command{cleared} report (@pxref{Format Strings}). The default is: @smallexample "%(justify(scrub(get_at(total_expr, 0)), 16, 16 + prepend_width, " " true, color)) %(justify(scrub(get_at(total_expr, 1)), 18, " " 36 + 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 ? \" \" * prepend_width : \"\")" "---------------- ---------------- ---------\n" @end smallexample @item --collapse @itemx -n By default ledger prints all accounts in an account tree. With @option{--collapse} it prints only the top level account specified. @item --collapse-if-zero Collapse the account display only if it has a zero balance. @item --color @itemx --ansi Use color if the terminal supports it. @item --columns @var{INT} Specify the width of the @command{register} report in characters. @item --count Direct ledger to report the number of items when appended to the @command{commodities}, @command{accounts} or @command{payees} command. @item --csv-format @var{FORMAT_STRING} Specify the format to use for the @command{csv} report (@pxref{Format Strings}). The default is: @smallexample "%(quoted(date))," "%(quoted(code))," "%(quoted(payee))," "%(quoted(display_account))," "%(quoted(commodity(scrub(display_amount))))," "%(quoted(quantity(scrub(display_amount))))," "%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\")))," "%(quoted(join(note | xact.note)))\n" @end smallexample @item --current Shorthand for @samp{--limit "date <= today"}. @item --daily @itemx -D Shorthand for @samp{--period "daily"}. @item --date @var{EXPR} Transform the date of the transaction using @var{EXPR}. @item --date-format @var{DATE_FORMAT} @itemx -y @var{DATE_FORMAT} Specify the format ledger should use to read and print dates (@pxref{Date and Time Format Codes}). @item --date-width @var{INT} Specify the width, in characters, of the date column in the @command{register} report. @item --datetime-format @var{DATETIME_FORMAT} Specify the format ledger should use to print datetimes. @item --dc Display register or balance in debit/credit format If you use @option{--dc} with either the @command{register} (reg) or @command{balance} (bal) commands, you will now get extra columns. The register goes from this: @smallexample 12-Mar-10 Employer Assets:Cash $100 $100 Income:Employer $-100 0 12-Mar-10 KFC Expenses:Food $20 $20 Assets:Cash $-20 0 12-Mar-10 KFC - Rebate Assets:Cash $5 $5 Expenses:Food $-5 0 12-Mar-10 KFC - Food & Reb.. Expenses:Food $20 $20 Expenses:Food $-5 $15 Assets:Cash $-15 0 @end smallexample @noindent To this: @smallexample 12-Mar-10 Employer Assets:Cash $100 0 $100 In:Employer 0 $100 0 12-Mar-10 KFC Expens:Food $20 0 $20 Assets:Cash 0 $20 0 12-Mar-10 KFC - Rebate Assets:Cash $5 0 $5 Expens:Food 0 $5 0 12-Mar-10 KFC - Food &.. Expens:Food $20 0 $20 Expens:Food 0 $5 $15 Assets:Cash 0 $15 0 @end smallexample @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 @option{--dc}: @smallexample $70 Assets:Cash $30 Expenses:Food $-100 Income:Employer -------------------- 0 @end smallexample @noindent And with @option{--dc} it becomes this: @smallexample $105 $35 $70 Assets:Cash $40 $10 $30 Expenses:Food 0 $100 $-100 Income:Employer -------------------------------------------- $145 $145 0 @end smallexample @item --depth @var{INT} Limit the depth of the account tree. In a balance report, for example, a @samp{--depth 2} statement will print balances only for accounts with two levels, i.e. @samp{Expenses:Entertainment} but not @samp{Expenses:Entertainment:Dining}. This is a display predicate, which means it only affects display, not the total calculations. @item --deviation Report each posting’s deviation from the average. It is only meaningful in the register and prices reports. @item --display @var{EXPR} Display only lines that satisfy the expression @var{EXPR}. @item --display-amount @var{EXPR} Apply a transformation to the @emph{displayed} amount. This happens after calculations occur. @item --display-total @var{EXPR} Apply a transformation to the @emph{displayed} total. This happens after calculations occur. @item --dow @itemx --days-of-week Group transactions by the day of the week. @smallexample @c command:validate $ ledger reg Expenses --dow --collapse @end smallexample @noindent Will print all Expenses totaled for each day of the week. @item --empty @itemx -E Include empty accounts in the report and in average calculations. @item --end @var{DATE} Specify the end @var{DATE} for a transaction to be considered in the report. All transactions on or after this date are ignored. @item --equity Related to the @command{equity} command (@pxref{The @command{equity} command}). Gives current account balances in the form of a register report. @item --exact Report beginning and ending of periods by the date of the first and last posting occurring in that period. @item --exchange @var{COMMODITY} @itemx -X @var{COMMODITY} Display values in terms of the given @var{COMMODITY}. The latest available price is used. The syntax @option{-X @var{COMMODITY1}:@var{COMMODITY2}} displays values in @var{COMMODITY1} in terms of @var{COMMODITY2} using the latest available price, but will not automatically covert any other commodities to @var{COMMODITY2}. Multiple @option{-X} arguments may be used on a single command-line (as in @option{-X COMMODITY1:COMMODITY2 -X COMMODITY3:COMMODITY2}), which is particularly useful for situations where many prices are available for reporting in terms of @var{COMMODITY2}, but only a few should be displayed that way. @item --flat Force the full names of accounts to be used in the balance report. The balance report will not use an indented tree. @item --force-color Output TTY color codes even if the TTY doesn't support them. Useful for TTYs that don't advertise their capabilities correctly. @item --force-pager Force Ledger to paginate its output. @item --forecast-while @var{VEXPR} @itemx --forecast @var{VEXPR} Continue forecasting while @var{VEXPR} is true. @item --forecast-years @var{INT} Forecast at most @var{INT} years into the future. @item --format @var{FORMAT_STRING} @itemx -F @var{FORMAT_STRING} Use the given format string to print output. @item --gain @itemx -G @itemx --change 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. @item --group-by @var{EXPR} Group transactions together in the @command{register} report. @var{EXPR} can be anything, although most common would be @code{payee} or @code{commodity}. The @code{tags()} function is also useful here. @item --group-title-format @var{FORMAT_STRING} Set the format for the headers that separates the report sections of a grouped report. Only has an effect with a @option{--group-by @var{EXPR}} register report. @smallexample @c command:validate $ ledger reg Expenses --group-by "payee" --group-title-format "------------------------ %-20(value) ---------------------\n" @end smallexample @smallexample ------------------------ 7-Eleven --------------------- 2011/08/13 7-Eleven Expenses:Auto:Misc $ 5.80 $ 5.80 ------------------------ AAA Dues --------------------- 2011/06/02 AAA Dues Expenses:Auto:Misc $ 215.00 $ 215.00 ------------------------ ABC Towing and Wrecking --------------------- 2011/03/17 ABC Towing and Wrec.. Expenses:Auto:Hobbies $ 48.20 $ 48.20 ... @end smallexample @item --head @var{INT} @itemx --first @var{INT} Print the first @var{INT} entries. Opposite of @option{--tail @var{INT}}. @item --historical @itemx -H Value commodities at the time of their acquisition. @item --immediate Evaluate calculations immediately rather than lazily. @item --inject Use @code{Expected} amounts in calculations. In case you know what amount a transaction should be, but the actual transaction has the wrong value you can use metadata to specify the expected amount: @smallexample @c input:validate 2012-03-12 Paycheck Income $-990; Expected:: $-1000.00 Checking @end smallexample Then using the command @code{ledger reg --inject=Expected Income} would treat the transaction as if the ``Expected Value'' was actual. @item --invert Change the sign of all reported values. @item --limit @var{EXPR} @itemx -l @var{EXPR} Only transactions that satisfy @var{EXPR} are considered in calculations and for display. @item --lot-dates Report the date on which each commodity in a balance report was purchased. @item --lot-notes @itemx --lot-tags Report the tag attached to each commodity in a balance report. @item --lot-prices Report the price at which each commodity in a balance report was purchased. @item --lots Report the date and price at which each commodity was purchased in a balance report. @item --lots-actual Preserve the uniqueness of commodities so they aren't merged during reporting without printing the lot annotations. @item --market @itemx -V Use the latest market value for all commodities. @item --meta @var{TAG} In the register report, prepend the transaction with the value of the given @var{TAG}. @item --meta-width @var{INT} Specify the width of the Meta column used for the @option{--meta @var{TAG}} options. @item --monthly @itemx -M Synonym for @samp{--period "monthly"}. @item --no-aliases Aliases are completely ignored. @item --no-color Suppress any color TTY output. @item --no-pager Direct output to stdout, avoiding pager program. @item --no-revalued Stop Ledger from showing @code{<Revalued>} postings. This option is useful in combination with the @option{--exchange} or @option{--market} option. @item --no-rounding Don't output @samp{<Adjustment>} postings. Note that this will cause the running total to often not add up! Its main use is for @option{--amount-data (-j)} and @option{--total-data (-J)} reports. @item --no-titles Suppress the output of group titles. @item --no-total Suppress printing the final total line in a balance report. @item --now @var{DATE} Define the current date in case you want to calculate in the past or future using @option{--current}. @item --only @var{FIXME} This is a postings predicate that applies after certain transforms have been executed, such as periodic gathering. @item --output @var{FILE} Redirect the output of ledger to the file defined in @file{FILE}. @item --pager @var{FILE} Direct output to @var{FILE} pager program. @item --payee @var{VEXPR} Sets a value expression for formatting the payee. In the @command{register} report this prevents the second entry from having a date and payee for each transaction. @item --payee-width @var{INT} Set the number of columns dedicated to the payee in the register report to @var{INT}. @item --pending Use only postings that are marked pending. @item --percent @itemx -% Calculate the percentage value of each account in balance reports. Only works for accounts that have a single commodity. @item --period @var{PERIOD_EXPRESSION} Define a period expression that sets the time period during which transactions are to be accounted. For a @command{register} report only the transactions that satisfy the period expression with be displayed. For a @command{balance} report only those transactions will be accounted in the final balances. @item --pivot @var{TAG} Produce a balance pivot report @emph{around} the given @var{TAG}. For example, if you have multiple cars and track each fuel purchase in @samp{Expenses:Auto:Fuel} and tag each fuel purchase with a tag identifying which car the purchase was for @samp{; Car: Prius}, then the command: @smallexample @c command:validate $ ledger bal Fuel --pivot "Car" --period "this year" @end smallexample @smallexample $ 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 @xref{Metadata values}. @item --plot-amount-format @var{FORMAT_STRING} Define the output format for an amount data plot. @xref{Visualizing with Gnuplot}. @item --plot-total-format @var{FORMAT_STRING} Define the output format for a total data plot. @xref{Visualizing with Gnuplot}. @item --prepend-format @var{FORMAT_STRING} Prepend @var{STR} to every line of the output. @item --prepend-width @var{INT} Reserve @var{INT} spaces at the beginning of each line of the output. @item --price @itemx -I Use the price of the commodity purchase for performing calculations. @item --pricedb-format @var{FORMAT_STRING} Set the format expected for the historical price file. @item --prices-format @var{FORMAT_STRING} Set the format for the @command{prices} report. @item --primary-date @itemx --actual-dates Show primary dates for all calculations (@pxref{Effective Dates}). @item --quantity @itemx -O Report commodity totals (this is the default). @item --quarterly Synonym for @samp{--period "quarterly"}. @item --raw In the @command{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. This can be useful for minor cleanups, like just aligning amounts. @item --real @itemx -R Account using only real transactions ignoring virtual and automatic transactions. @item --register-format @var{FORMAT_STRING} Define the output format for the @command{register} report. @item --related In a @command{register} report show the related account. This is the other @emph{side} of the transaction. @item --related-all Show all postings in a transaction, similar to @option{--related} but show both @emph{sides} of each transaction. @item --revalued Report discrepancy in values for manual reports by inserting @code{<Revalued>} postings. This is implied when using the @option{--exchange} or @option{--market} option. @item --revalued-only Show only @code{<Revalued>} postings. @item --revalued-total @var{FIXME} Display the sum of the revalued postings as the running total, which serves to show unrealized capital in a gain/losses report. @item --rich-data @itemx --detail When generating a ledger transaction from a CSV file using the @command{convert} command, add CSV, Imported, and UUID metadata. @item --seed @var{INT} Set the random seed to @var{INT} for the @code{generate} command. Used as part of development testing. @item --sort @var{VEXPR} @itemx -S @var{VEXPR} Sort the @command{register} report based on the value expression given to sort. @item --sort-all @var{FIXME} @value{FIXME:UNDOCUMENTED} @item --sort-xacts @var{VEXPR} @itemx --period-sort @var{VEXPR} Sort the postings within transactions using the given value expression. @item --start-of-week @var{INT} Tell ledger to use a particular day of the week to start its ``weekly'' summary. @samp{--start-of-week=1} specifies Monday as the start of the week. @item --subtotal @itemx -s Cause all transactions in a @command{register} report to be collapsed into a single, subtotaled transaction. @item --tail @var{INT} @itemx --last @var{INT} Report only the last @var{INT} entries. Only useful in a @command{register} report. @item --time-report Add two columns to the balance report to show the earliest checkin and checkout times for timelog entries. @item --total @var{VEXPR} @itemx -T @var{VEXPR} Define a value expression used to calculate the total in reports. @item --total-data @itemx -J Show only dates and totals to format the output for plots. @item --total-width @var{INT} Set the width of the total field in the register report. @item --truncate @var{CODE} Indicates how truncation should happen when the contents of columns exceed their width. Valid arguments are @samp{leading}, @samp{middle}, and @samp{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 Show only unbudgeted postings. @item --uncleared @itemx -U Use only uncleared transactions in calculations and reports. @item --unrealized Show generated unrealized gain and loss accounts in the balance report. @item --unrealized-gains @var{STR} Allow the user to specify what account name should be used for unrealized gains. Defaults to @samp{"Equity:Unrealized Gains"}. Often set in one's @file{~/.ledgerrc} file to change the default. @item --unrealized-losses @var{STR} Allow the user to specify what account name should be used for unrealized losses. Defaults to @samp{"Equity:Unrealized Losses"}. Often set in one's @file{~/.ledgerrc} file to change the default. @item --unround Perform all calculations without rounding and display results to full precision. @item --values Shows the values used by each tag when used in combination with the @command{tags} command. @item --weekly @itemx -W Synonym for @samp{--period "weekly"}. @item --wide Let the register report use 132 columns instead of 80 (the default). Identical to @samp{--columns "132"}. @item --yearly @itemx -Y Synonym for @samp{--period "yearly"}. @end ftable @node Basic options, Report filtering, Report Options, Detailed Option Description @subsection Basic options 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: @ftable @option @item --help @itemx -h Display the man page for @file{ledger}. @item --version Print 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 @var{FILE} @itemx -f @var{FILE} Read @file{FILE} as a ledger file. @var{FILE} can be @samp{-} which is a synonym for @samp{/dev/stdin}. 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 @var{FILE} @itemx -o @var{FILE} Redirect output from any command to @file{FILE}. By default, all output goes to standard output. @item --init-file @var{FILE} @itemx -i @var{FILE} Causes @file{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 on the command-line, but put each option on its own line. Here is an example init file: @smallexample @c input:validate --price-db ~/finance/.pricedb --wide ; ~/.ledgerrc ends here @end smallexample Option settings on the command-line or in the environment always take precedence over settings in the init file. @item --account @var{STR} @itemx -a @var{STR} Specify the default account which QIF file postings are assumed to relate to. @end ftable @node Report filtering, Output customization, Basic options, Detailed Option Description @subsection Report filtering These options change which postings affect the outcome of a report, in ways other than just using regular expressions: @ftable @option @item --current @itemx -c Display only transactions occurring on or before the current date. @item --begin @var{DATE} @itemx -b @var{DATE} Constrain 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 @var{EXPR}} to constrain what is displayed). @item --end @var{DATE} @itemx -e @var{DATE} Constrain the report so that transactions on or after @var{DATE} are not considered. @item --period @var{PERIOD_EXPRESSION} @itemx -p @var{PERIOD_EXPRESSION} Set 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 @samp{last June} or @samp{next month}. For more details on period expressions, see @ref{Period Expressions}. @item --period-sort @var{VEXPR} Sort 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: @c TODO: the parameter to --period-sort was -At, which doesn't seem to work any longer @smallexample @c command:validate $ ledger -M --period-sort total reg ^Expenses @end smallexample @item --cleared @itemx -C Display only postings whose transaction has been marked ``cleared'' (by placing an asterisk to the right of the date). @item --uncleared @itemx -U Display only postings whose transaction has not been marked ``cleared'' (i.e., if there is no asterisk to the right of the date). @item --real @itemx -R Display 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 --actual @itemx -L Display only actual postings, and not those created by automated transactions. @item --related @itemx -r Display 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 @c input:94C5675 2004/03/20 Safeway Expenses:Food $65.00 Expenses:Cash $20.00 Assets:Checking $-85.00 @end smallexample And the register command was: @smallexample @c command:94C5675 $ ledger -f example.dat -r register food @end smallexample The following would be printed, showing the postings related to the posting that matched: @smallexample @c output:94C5675 04-Mar-20 Safeway Expenses:Cash $20.00 $20.00 Assets:Checking $-85.00 $-65.00 @end smallexample @item --budget Useful for displaying how close your postings meet your budget. @option{--add-budget} also shows unbudgeted postings, while @option{--unbudgeted} shows only those. @option{--forecast @var{VEXPR}} is a related option that projects your budget into the future, showing how it will affect future balances. @xref{Budgeting and Forecasting}. @item --limit @var{EXPR} @itemx -l @var{EXPR} Limit which postings take part in the calculations of a report. @item --amount @var{EXPR} @itemx -t @var{EXPR} Change 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}. @item --total @var{VEXPR} @itemx -T @var{VEXPR} Set the value expression used for the ``totals'' column in the @command{register} and @command{balance} reports. @end ftable @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 @c Commands}), @code{OPTIONS} can occur anywhere, and @c @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 @c ``dining'' for any payee other than ``chang'' the following three @c commands would be 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 Option Description @subsection Output customization These options affect only the output, but not which postings are used to create it: @ftable @option @item --collapse @itemx -n Cause transactions in a @command{register} report with multiple postings to be collapsed into a single, subtotaled transaction. @item --subtotal @itemx -s Cause all transactions in a @command{register} report to be collapsed into a single, subtotaled transaction. @item --by-payee @itemx -P Report subtotals by payee. @item --empty @itemx -E Include even empty accounts in the @command{balance} report. @item --weekly @itemx -W Report 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}. @item --monthly @itemx -M Report posting totals by month. @item --yearly @itemx -Y Report posting totals by year. For more complex periods, use @option{--period}. @c TODO end this sentence @item --period @var{PERIOD_EXPRESSION} Option described above. @item --dow Report posting totals for each day of the week. This is an easy way to see if weekend spending is more than on weekdays. @item --sort @var{VEXPR} @itemx -S @var{VEXPR} Sort a report by comparing the values determined using the value expression @var{VEXPR}. For example, using @samp{-S "-abs(total)"} in the @command{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 @var{TAG} Produce a pivot table around the @var{TAG} provided. This requires meta data using valued tags. @item --wide @itemx -w Cause the default @command{register} report to assume 132 columns instead of 80. @item --head @var{INT} Cause only the first @var{INT} transactions to be printed. This is different from using the command-line utility @file{head}, which would limit to the first @var{INT} postings. @option{--tail @var{INT}} outputs only the last @var{INT} 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 @var{FILE} Tell Ledger to pass its output to the given @var{FILE} 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 --no-pager Tell Ledger to @emph{not} pass its output to a pager program; useful when a pager is set by default. @item --average @itemx -A Report the average posting value. @item --deviation @itemx -D Report each posting's deviation from the average. It is only meaningful in the @command{register} and @command{prices} reports. @item --percent @itemx -% Show account subtotals in the @command{balance} report as percentages of the parent account. @c @option{--totals} include running total information in the @c @command{xml} report. @item --amount-data @itemx -j Change the @command{register} report so that it prints 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 --total-data @itemx -J Change the @command{register} report so that it prints nothing but the date and total columns, without commodities. @item --display @var{EXPR} @itemx -d @var{EXPR} Limit which postings or accounts are 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 @c command:validate $ ledger -d "d>=[last month]" reg checking @end smallexample The output from this command is very different from the following, whose running total includes only postings from the last month onward: @smallexample @c command:validate $ 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 (using @option{--period @var{PERIOD_EXPRESSION} (-p)}), or simply a display restricted to the reporting range (using @option{--display @var{EXPR} (-d)}). @item --date-format @var{DATE_FORMAT} @itemx -y @var{DATE_FORMAT} Change the basic date format used by reports. The default uses a date like @samp{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 @option{--date-format @var{DATE_FORMAT}} in the Ledger initialization file @file{~/.ledgerrc} (or the file referred to by @env{LEDGER_INIT}). @item --format @var{FORMAT_STRING} @itemx -F @var{FORMAT_STRING} Set 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 @var{FORMAT_STRING} Define the output format for the @command{balance} report. The default (defined in @file{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 @var{FORMAT_STRING} Define 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 @var{FORMAT_STRING} Define the output format for the @command{register} report. The default (defined in @file{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 @var{FORMAT_STRING} Set the format for @command{csv} reports. The default is: @smallexample "%(quoted(date)), %(quoted(code)), %(quoted(payee)), %(quoted(display_account)), %(quoted(commodity(scrub(display_amount)))), %(quoted(quantity(scrub(display_amount)))), %(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))), %(quoted(join(note | xact.note)))\n" @end smallexample @item --plot-amount-format @var{FORMAT_STRING} Set the format for amount plots, using the @option{--amount-data (-j)} option. The default is: @smallexample "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_amount)))\n" @end smallexample @item --plot-total-format @var{FORMAT_STRING} Set the format for total plots, using the @option{--total-data (-J)} option. The default is: @smallexample "%(format_date(date, \"%Y-%m-%d\")) %(quantity(scrub(display_total)))\n" @end smallexample @item --pricedb-format @var{FORMAT_STRING} Set 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 @var{FORMAT_STRING} Set 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 @end ftable @node Commodity reporting, Environment variables, Output customization, Detailed Option Description @subsection Commodity reporting These options affect how commodity values are displayed: @ftable @option @item --price-db @var{FILE} Set the file that is used for recording downloaded commodity prices. It is always read on startup, to determine historical prices. Other settings can be placed in this file manually, to prevent downloading quotes for a specific commodity, for example. This is done by adding a line like the following: @smallexample @c input:validate ; Don't download quotes for the dollar, or timelog values N $ N h @end smallexample @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. The format of the file can be changed by telling ledger to use the @option{--pricedb-format @var{FORMAT_STRING}} you define. @item --price-exp @var{INT} @itemx --leeway @var{INT} @itemx -Z @var{INT} Set the expected freshness of price quotes, in @var{INT} 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. @item --download @itemx -Q Cause quotes to be automagically downloaded, as needed, by running a script named @file{getquote} and expecting that script to return a value understood by ledger. A sample implementation of a @file{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 ftable 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{--amount @var{EXPR} (-t)} and @option{--total @var{VEXPR} (-T)} options. However, there are also several ``default'' reports, which will satisfy most users' basic reporting needs: @ftable @option @item --quantity @itemx -O Report commodity totals (this is the default). @item --basis @itemx -B Report the cost basis for all postings. @item --market @itemx -V Use the last known value for commodities to calculate final values. @item --gain @itemx -G Report the net gain/loss for all commodities in the report that have a price history. @end ftable Often you will be more interested in the value of your entire holdings, in your preferred currency. It might be nice to know you hold 10,000 shares of PENNY, but you are more interested in whether or not that is worth $1000.00 or $10,000.00. However, the current day value of a commodity can mean different things to different people, depending on the accounts involved, the commodities, the nature of the transactions, etc. @findex --now @var{DATE} @findex --market @findex --exchange @var{COMMODITY} When you specify @option{--market (-V)}, or @option{--exchange @var{COMMODITY} (-X)}, you are requesting that some or all of the commodities be valuated as of today (or whatever @option{--now @var{DATE}} is set to). But what does such a valuation mean? This meaning is governed by the presence of a @var{VALUE} meta-data property, whose content is an expression used to compute that value. If no @var{VALUE} property is specified, each posting is assumed to have a default, as if you'd specified a global, automated transaction as follows: @smallexample @c input:validate = expr true ; VALUE:: market(amount, date, exchange) @end smallexample This definition emulates the present day behavior of @option{--market (-V)} and @option{--exchange @var{COMMODITY} (-X)} (in the case of @samp{-X}, the requested commodity is passed via the string @samp{exchange} above). @cindex Euro conversion One thing many people have wanted to do is to fixate the valuation of old European currencies in terms of the Euro after a certain date: @smallexample @c input:validate = expr commodity == "DM" ; VALUE:: date < [Jun 2008] ? market(amount, date, exchange) : 1.44 EUR @end smallexample This says: If @option{--now @var{DATE}} is some old date, use market prices as they were at that time; but if @option{--now @var{DATE}} 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 cannot have a different future value: @smallexample @c input:validate = /^Expenses:/ ; VALUE:: market(amount, post.date, exchange) @end smallexample This says the future valuation is the same as the valuation at the time of posting. @code{post.date} equals the posting's date, while just 'date' is the value of @option{--now @var{DATE}} (defaults to today). Or how about valuating miles based on a reimbursement rate during a specific time period: @smallexample @c input:validate = expr commodity == "miles" and date >= [2007] and date < [2008] ; VALUE:: market($1.05, date, exchange) @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 Euro, Ledger shall convert $1.05 to Euro by whatever means are appropriate for dollars. Note that you can have a valuation expression specific to a particular posting or transaction, by overriding these general defaults using specific meta-data: @smallexample @c input:validate 2010-12-26 Example Expenses:Food $20 ; Just to be silly, always valuate *these* $20 as 30 DM, no matter what ; the user asks for with -V or -X ; VALUE:: 30 DM Assets:Cash @end smallexample This example demonstrates that your value expression should be as symbolic as possible, using terms like 'amount' and 'date', rather than specific amounts and dates. Also, you should pass the amount along to the function 'market' so it can be further revalued if the user has asked for a specific currency. Or, if it better suits your accounting, you can be less symbolic, which allows you to report most everything in EUR if you use @samp{-X EUR}, except for certain accounts or postings which should always be valuated in another currency. For example: @c TODO is this example missing the actual line to get the effect? @c it looks like it only contains a match, but no effect @smallexample @c input:validate = /^Assets:Brokerage:CAD$/ ; Always report the value of commodities in this account in ; terms of present day dollars, despite what was asked for ; on the command-line VALUE:: market(amount, date, @samp{$}) @end smallexample @cindex FIFO/LIFO @cindex LIFO/FIFO @findex --lots @findex --lot-prices @findex --exchange @var{COMMODITY} @findex --historical @findex --basis @findex --price 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 @option{--lots} are not being displayed, then it will appear to balance against any lot of AAPL. @cindex adorned commodity @findex --lot-prices If you specify an adorned commodity, like AAPL @{$10.00@}, it will also balance against itself, and against any AAPL if @option{--lots} is not specified. But if you do specify @option{--lot-prices}, for example, then it will balance against that specific price for AAPL. Normally when you use @option{--exchange @var{COMMODITY} (-X)} to request that amounts be reported in a specific commodity, Ledger uses these values: @itemize @item Register Report For the @command{register} report, use the value of that commodity on the date of the posting being reported, with a @samp{<Revalued>} posting added at the end if today's value is different from the value of the last posting. @item Balance Report For the @command{balance} report, use the value of that commodity as of today. @end itemize You can now specify @option{--historical (-H)} to ask that all valuations for any amount be done relative to the date that amount was encountered. You can also now use @option{--exchange @var{COMMODITY} (-X)} (and @option{--historical (-H)}) in conjunction with @option{--basis (-B)} and @option{--price (-I)}, to see valuation reports of just your basis costs or lot prices. Finally, sometimes, you may seek to only report one (or some subset) of the commodities in terms of another commodity. In this situation, you can use the syntax @option{--exchange @var{COMMODITY1}:@var{COMMODITY2}} to request that ledger always display @var{COMMODITY1} in terms of @var{COMMODITY2}, but you want no other commodities to be automatically displayed in terms of @var{COMMODITY2} without additional @option{--exchange} options. For example, if you wanted to report EUR and BTC in terms of USD, but report all other commodities without conversion to USD, you could use: @option{--exchange EUR:USD --exchange BTC:USD}. @node Environment variables, , Commodity reporting, Detailed Option Description @subsection Environment variables Every option to ledger may be set using an environment variable if the option has a long name. For example setting the environment variable @samp{@env{LEDGER_DATE_FORMAT}="%d.%m.%Y"} will have the same effect as specifying @samp{@option{--date-format} '%d.%m.%Y'} on the command-line. Options on the command-line always take precedence over environment variable settings, however. Note that you may also permanently specify option values by placing option settings in the file @file{~/.ledgerrc} one option per line, for example: @smallexample @c input:validate --pager /bin/cat @end smallexample @node Period Expressions, , Detailed Option Description, Command-Line Syntax @section Period Expressions @c TODO use @var below A period expression indicates a span of time, or a reporting interval, or both. The full syntax is: @smallexample [INTERVAL] [BEGIN] [END] @end smallexample The optional @var{INTERVAL} part may be any one of: @smallexample every day every week every month every quarter every year every N days # N is any integer every N weeks every N months every N quarters every N years daily weekly biweekly monthly bimonthly quarterly yearly @end smallexample After the interval, a begin time, end time, both or neither may be specified. As for the begin time, it can be either of: @smallexample from <SPEC> since <SPEC> @end smallexample The end time can be either of: @smallexample to <SPEC> until <SPEC> @end smallexample Where @var{SPEC} can be any of: @smallexample 2004 2004/10 2004/10/1 10/1 october oct this week # or day, month, quarter, year next week last week @end smallexample The beginning and ending can be given at the same time, if it spans a single period. In that case, just use @var{SPEC} by itself. In that case, the period @samp{oct}, for example, will cover all the days in October. The possible forms are: @smallexample <SPEC> in <SPEC> @end smallexample Here are a few examples of period expressions: @smallexample monthly monthly in 2004 weekly from oct weekly from last month from sep to oct from 10/1 to 10/5 monthly until 2005 from apr until nov last oct weekly last august @end smallexample @node Budgeting and Forecasting, Time Keeping, Command-Line Syntax, Top @chapter Budgeting and Forecasting @menu * Budgeting:: * Forecasting:: @end menu @node Budgeting, Forecasting, Budgeting and Forecasting, Budgeting and Forecasting @section Budgeting @findex --budget @findex --add-budget @findex --unbudgeted @findex --monthly Keeping a budget allows you to pay closer attention to your income and expenses, by reporting how far your actual financial activity is from your expectations. To start keeping a budget, put some periodic transactions (@pxref{Periodic Transactions}) at the top of your ledger file. A periodic transaction is almost identical to a regular transaction, except that it begins with a tilde and has a period expression in place of a payee. For example: @smallexample @c input:validate ~ Monthly Expenses:Rent $500.00 Expenses:Food $450.00 Expenses:Auto:Gas $120.00 Expenses:Insurance $150.00 Expenses:Phone $125.00 Expenses:Utilities $100.00 Expenses:Movies $50.00 Expenses $200.00 ; all other expenses Assets ~ Yearly Expenses:Auto:Repair $500.00 Assets @end smallexample These two periodic transactions give the usual monthly expenses, as well as one typical yearly expense. For help on finding out what your average monthly expenses are for any category, use a command like: @smallexample @c command:validate $ ledger -p "this year" --monthly --average balance ^expenses @end smallexample The reported totals are the current year's average for each account. Once these periodic transactions are defined, creating a budget report is as easy as adding @option{--budget} to the command-line. For example, a typical monthly expense report would be: @smallexample @c command:validate $ ledger --monthly register ^expenses @end smallexample To see the same report balanced against your budget, use: @smallexample @c command:validate $ ledger --budget --monthly register ^expenses @end smallexample A budget report includes only those accounts that appear in the budget. To see all expenses balanced against the budget, use @option{--add-budget}. You can even see only the unbudgeted expenses using @option{--unbudgeted}: @smallexample @c command:validate $ ledger --unbudgeted --monthly register ^expenses @end smallexample You can also use these flags with the @command{balance} command. @node Forecasting, , Budgeting, Budgeting and Forecasting @section Forecasting @findex --forecast @var{VEXPR} Sometimes it's useful to know what your finances will look like in the future, such as determining when an account will reach zero. Ledger makes this easy to do, using the same periodic transactions as are used for budgeting. An example forecast report can be generated with: @smallexample @c command:validate $ ledger --file drewr3.dat --forecast "T>@{\$-500.00@}" register ^assets ^liabilities @end smallexample This report continues outputting postings until the running total is greater than $-500.00. A final posting is always shown, to inform you what the total afterwards would be. Forecasting can also be used with the @command{balance} report, but by date only, and not against the running total: @smallexample @c command:validate $ ledger --forecast "d<[2010]" bal ^assets ^liabilities @end smallexample @node Time Keeping, Value Expressions, Budgeting and Forecasting, Top @chapter Time Keeping @findex --day-break @anchor{timelog} Ledger directly supports ``timelog'' entries, which have this form: @smallexample @c input:validate 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 @option{--day-break} to break them up in the report). The number of seconds between check-in and check-out is accumulated as time to that ACCOUNT. If the checkout uses a capital @samp{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 @file{org2tc} script developed by John Wiegley. These timelog entries can appear in a separate file, or directly in your main ledger file. The initial @samp{i} and @samp{o} characters count as Ledger ``directives'', and are accepted anywhere that ordinary transactions are valid. @node Value Expressions, Format Strings, Time Keeping, Top @chapter Value Expressions @findex --limit @var{EXPR} @findex --display @var{EXPR} 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 @option{--limit @var{EXPR} (-l)}) or displayed (option @option{--display @var{EXPR} (-d)}). @item For sorting criteria, to yield the sort key. @item 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 functions and variables. @c A function's argument is whatever follows it. The following is @c a display predicate that I use with the @command{balance} command: @c @smallexample @c ledger -d '/^Liabilities/?T<0:UT>100' balance @c @end smallexample @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 command shows only transactions from the beginning of the current month, while still calculating the running balance based on all transactions: @smallexample @c command:validate $ ledger -d "d>[this month]" register checking @end smallexample The advantage of 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 postings: @smallexample @c command:validate $ ledger -b "this month" register checking @end smallexample @menu * Variables:: * Functions:: * Operators:: * Complex expressions:: @end menu @node Variables, Functions, Value Expressions, Value Expressions @section Variables @findex --amount @var{EXPR} @findex --total @var{VEXPR} Below are the one letter variables available in any value expression. For the @command{register} and @command{print} commands, these variables relate to individual postings, and sometimes the account affected by a posting. For the @command{balance} command, these variables relate to accounts, often with a subtle difference in meaning. The use of each variable for both is specified. @table @code @item t This maps to whatever the user specified with @option{--amount @var{EXPR} (-t)}. In a @command{register} report, @option{--amount @var{EXPR} (-t)} changes the value column; in a @command{balance} report, it has no meaning by default. If @option{--amount @var{EXPR} (-t)} was not specified, the current report style's value expression is used. @item T This maps to whatever the user specified with @option{--total @var{VEXPR} (-T)}. In a register report, @option{--total @var{VEXPR} (-T)} changes the totals column; in a balance report, this is the value given for each account. If @option{--total @var{VEXPR} (-T)} was not specified, the current report style's value expression is used. @item m This is always the present moment/date. @end table @menu * Posting/account details:: * Calculated totals:: @end menu @node Posting/account details, Calculated totals, Variables, Variables @subsection Posting/account details @table @code @item d A posting's date, as the number of seconds past the epoch. This is always ``today'' for an account. @item a The posting's amount; the balance of an account, without considering children. @item b The cost of a posting; the cost of an account, without its children. @item v 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}. @item l The depth (``level'') of an account. If an account has one parent, its depth is one. @item n The index of a posting, or the count of postings affecting an account. @item X @samp{1} if a posting's transaction has been cleared, @samp{0} otherwise. @item R @samp{1} if a posting is not virtual, @samp{0} otherwise. @item Z @samp{1} if a posting is not automated, @samp{0} otherwise. @end table @node Calculated totals, , Posting/account details, Variables @subsection Calculated totals @table @code @item O The total of all postings seen so far, or the total of an account and all its children. @item N The total count of postings affecting an account and all its children. @end table @node Functions, Operators, Variables, Value Expressions @section Functions The available one letter functions are: @table @code @item - Negates the argument. @item U The absolute (unsigned) value of the argument. @item S Strips the commodity from the argument. @item P The present market value of the argument. The syntax @samp{P(x,d)} is supported, which yields the market value at time @samp{d}. If no date is given, then the current moment is used. @end table @node Operators, Complex expressions, Functions, Value Expressions @section Operators The binary and ternary operators, in order of precedence, are: @enumerate @item @code{* /} @item @code{+ -} @item @code{! < > =} @item @code{& | ?:} @end enumerate @menu * Unary Operators:: * Binary Operators:: @end menu @node Unary Operators, Binary Operators, Operators, Operators @subsection Unary Operators @code{not} @code{neg} @node Binary Operators, , Unary Operators, Operators @subsection Binary Operators @code{==} @code{<} @code{<=} @code{>} @code{>=} @code{and} @code{or} @code{+} @code{-} @code{*} @code{/} @code{QUERY} @code{COLON} @code{CONS} @code{SEQ} @code{DEFINE} @code{LOOKUP} @code{LAMBDA} @code{CALL} @code{MATCH} @node Complex expressions, , Operators, Value Expressions @section Complex expressions More complicated expressions are possible using: @table @code @item "amount == COMMODITY AMOUNT" The amount can be any kind of amount supported by ledger, with or without a commodity. Use this for decimal values. @item /REGEX/ @itemx account =~ /REGEX/ A regular expression that matches against an account's full name. If a posting, this will match against the account affected by the posting. @item @@/REGEX/ @itemx expr payee =~ /REGEX/ A regular expression that matches against a transaction's payee name. @item %/REGEX/ @itemx tag(REGEX) A regular expression that matches against a transaction's tags. @item expr date =~ /REGEX/ Useful for specifying a date in plain terms. For example, you could say @samp{expr date =~ /2014/}. @item expr comment =~ /REGEX/ A regular expression that matches against a posting's comment field. This searches only a posting's field, not the transaction's note or comment field. For example, @code{ledger reg "expr" "comment =~ /landline/"} will match: @smallexample @c input:validate 2014/1/29 Phone bill Assets:Checking $50.00 Expenses:Phone $-50.00 ; landline bill @end smallexample but will not match: @smallexample @c input:validate 2014/1/29 Phone bill ; landline bill ; landline bill Assets:Checking $50.00 Expenses:Phone $-50.00 @end smallexample To match the latter, use @samp{ledger reg "expr" "note =~ /landline/"} instead. @item expr note =~ /REGEX/ A regular expression that matches against a transaction's note field. This searches all comments in the transaction, including comments on individual postings. Thus, @samp{ledger reg "expr" "note =~ /landline/"} will match both all the three examples below: @smallexample @c input:validate 2014/1/29 Phone bill Assets:Checking $50.00 Expenses:Phone $-50.00 ; landline bill @end smallexample @smallexample @c input:validate 2014/1/29 Phone bill ; landline bill Assets:Checking $50.00 Expenses:Phone $-50.00 @end smallexample @smallexample @c input:validate 2014/1/29 Phone bill ; landline bill Assets:Checking $50.00 Expenses:Phone $-50.00 @end smallexample @item (EXPR) A sub-expression is nested in parenthesis. This can be useful passing more complicated arguments to functions, or for overriding the natural precedence order of operators. @item expr base =~ /REGEX/ A regular expression that matches against an account's base name. If a posting, this will match against the account affected by the posting. @item expr code =~ /REGEX/ A regular expression that matches against the transaction code (the text that occurs between parentheses before the payee). @end table The @command{query} command can be used to see how Ledger interprets your query. This can be useful if you are not getting the results you expect (@pxref{Pre-Commands}). @menu * Miscellaneous:: @end menu @node Miscellaneous, , Complex expressions, Complex expressions @subsection Miscellaneous The following Ledger journal data (saved as @file{expr.dat}) is used to explain the behaviour of the functions and variables below: @anchor{expr.dat} @smallexample @c input:3406FC1 2015/01/16 * (C0D3) Payee Assets:Cash ¤ -123,45 ; Payee: PiggyBank Expenses:Office Supplies @end smallexample @defun abs value @defunx U value Return the absolute value of the given @var{value}, e.g. @var{amount}. @smallexample @c command:3406FC1 $ ledger -f expr.dat --format "%(account) %(abs(amount))\n" reg assets @end smallexample @smallexample @c output:3406FC1 Assets:Cash ¤ 123,45 @end smallexample @end defun @defun amount_expr Return the calculated amount of the posting according to the @option{--amount} option. @end defun @defun ansify_if value color bool Render the given @var{expression} as a string, applying the proper ANSI escape codes to display it in the given @var{color} if @var{bool} is true. It typically checks the value of the option @option{--color}. Since ANSI escape codes include non-printable character sequences, such as escape @kbd{^[} the following example may not appear as the final result on the command-line. @smallexample @c command:4D836EE,with_input:3406FC1 $ ledger -f expr.dat --format "%(ansify_if(account, blue, options.color))\n" reg @end smallexample @smallexample @c output:4D836EE Assets:Cash Expenses:Office Supplies @end smallexample @end defun @defun ceiling value Return the next integer of @var{value} toward @math{+}infinity. @smallexample @c command:FF9C18C,with_input:3406FC1 $ ledger -f expr.dat --format "%(account) %(ceiling(amount))\n" reg @end smallexample @smallexample @c output:FF9C18C Assets:Cash ¤ -123,00 Expenses:Office Supplies ¤ 124,00 @end smallexample @end defun @defvar code Return the transaction code, the string between the parenthesis after the date. @smallexample @c command:46FCFD3,with_input:3406FC1 $ ledger -f expr.dat --format "%(account) %(code)\n" reg assets @end smallexample @smallexample @c output:46FCFD3 Assets:Cash C0D3 @end smallexample @end defvar @defvar commodity Return the commodity of the posting amount. @end defvar @smallexample @c command:2CD27D7,with_input:3406FC1 $ ledger -f expr.dat --format "%(account) %(commodity)\n" reg @end smallexample @smallexample @c output:2CD27D7 Assets:Cash ¤ Expenses:Office Supplies ¤ @end smallexample @defvar date Return the date of the posting. @end defvar @smallexample @c command:67EBA45,with_input:3406FC1 $ ledger -f expr.dat --format "%(date) %(account)\n" reg assets @end smallexample @smallexample @c output:67EBA45 2015/01/16 Assets:Cash @end smallexample @defvar display_amount @defvarx t @value{FIXME:UNDOCUMENTED} @end defvar @c FIXME @defvar display_total @defvarx T @value{FIXME:UNDOCUMENTED} @end defvar @defun floor value Return the next integer of @var{value} toward @math{-}infinity. @smallexample @c command:4FDC7C5,with_input:3406FC1 $ ledger -f expr.dat --format "%(account) %(floor(amount))\n" reg @end smallexample @smallexample @c output:4FDC7C5 Assets:Cash ¤ -124,00 Expenses:Office Supplies ¤ 123,00 @end smallexample @end defun @defun format string Evaluate @var{string} as format just like the @option{--format} option. @end defun @defun format_date date format Return the @var{date} as a string using @var{format}. See @code{strftime (3)} for format string details. @smallexample @c command:9605B13,with_input:3406FC1 $ ledger -f expr.dat --format "%(format_date(date, '%A, %B %d. %Y'))\n" reg assets @end smallexample @smallexample @c output:9605B13 Friday, January 16. 2015 @end smallexample @end defun @defun format_datetime datetime format Return the @var{datetime} as a string using @var{format}. Refer to @code{strftime (3)} for format string details. @end defun @defun get_at sequence index Return the value in @var{sequence} at @var{index}. The first element is @var{index} 0. @value{InternalUseOnly} @end defun @defun is_seq value Return true if @var{value} is a sequence. @value{InternalUseOnly} @end defun @defun join value Replace all newlines in @var{value} with @code{\n}. @end defun @defun justify value first_width latter_width right_justify colorize Right or left justify the string representing @var{value}. The width of the field in the first line is given by @var{first_width}. For subsequent lines the width is given by @var{latter_width}. If @var{latter_width=-1}, then @var{first_width} is used for all lines. If @var{right_justify=true} then the field is right justified within the width of the field. If it is @var{false}, then the field is left justified and padded to the full width of the field. If @var{colorize} is true, then ledger will honor color settings. @smallexample @c command:082FB27,with_input:3406FC1 $ ledger -f expr.dat --format "»%(justify(account, 30, 30, true))«\n" reg @end smallexample @smallexample @c output:082FB27 » Assets:Cash« » Expenses:Office Supplies« @end smallexample @end defun @defun market value datetime @defunx P Return the price of @var{value} at @var{datetime}. Note that @var{datetime} must be surrounded by brackets in order to be parsed correctly, e.g. @code{[2012/03/23]}. @end defun @defun nail_down @value{FIXME:UNDOCUMENTED} @end defun @defvar now @defvarx d @defvarx m Return the current datetime. @end defvar @defvar options A variable that allows access to the values of the given command-line options using the long option names, e.g. to see whether @option{--daily} or @option{-D} was given use @code{option.daily}. @smallexample @c command:C1FC7A7,with_input:3406FC1 $ ledger -f expr.dat -X $ -D --format "%(options.daily) %(options.exchange)\n" reg assets @end smallexample @smallexample @c output:C1FC7A7 true $ @end smallexample @end defvar @defun percent value_a value_b Return the percentage of @var{value_a} in relation to @var{value_b} (used as 100%) @smallexample @c command:04959BF,with_input:3406FC1 $ ledger -f expr.dat --format "%(percent(amount, 200))\n" reg @end smallexample @smallexample @c output:04959BF -61.73% 61.73% @end smallexample @end defun @defun print value Print @var{value} to stdout. @value{InternalUseOnly} @end defun @defun quantity value Return the quantity of @var{value} for values that have a per-unit cost. @end defun @defun quoted expression Surround @var{expression} with double-quotes. @smallexample @c command:EAD8AA7,with_input:3406FC1 $ ledger -f expr.dat --format "%(quoted(account)) %(quoted(amount))\n" reg @end smallexample @smallexample @c output:EAD8AA7 "Assets:Cash" "¤ -123,45" "Expenses:Office Supplies" "¤ 123,45" @end smallexample @end defun @defun round @value{FIXME:UNDOCUMENTED} @end defun @defun rounded @value{FIXME:UNDOCUMENTED} @end defun @defun roundto value n Return @var{value} rounded to @var{n} digits. Does not affect formatting. @smallexample @c command:B4DFB9F,with_input:3406FC1 $ ledger -f expr.dat --format "%(account) %(roundto(amount, 1))\n" reg @end smallexample @smallexample @c output:B4DFB9F Assets:Cash ¤ -123,40 Expenses:Office Supplies ¤ 123,50 @end smallexample @end defun @defun scrub value Clean @var{value} using various transformations such as @code{round}, stripping value annotations, and more. @end defun @defun should_bold Return true if expression given to @option{--bold-if} evaluates to true. @value{InternalUseOnly} @end defun @defun strip value @defunx S Strip value annotation from @var{value}. @end defun @defun to_amount value Convert @var{value} to an amount. @value{InternalUseOnly} @end defun @defun to_balance value Convert @var{value} to a balance. @value{InternalUseOnly} @end defun @defun to_boolean value Convert @var{value} to a boolean. @value{InternalUseOnly} @end defun @defun to_date value Convert @var{value} to a date. @value{InternalUseOnly} @end defun @defun to_datetime value Convert @var{value} to a datetime. @value{InternalUseOnly} @end defun @defun to_int value @defunx int value Return the integer value for @var{value}. @smallexample @c command:0B0CBA1,with_input:3406FC1 $ ledger -f expr.dat --format "%(1 + to_int('1'))\n%(2,5 + int(2,5))\n" reg assets @end smallexample @smallexample @c output:0B0CBA1 2 4.5 @end smallexample @end defun @defun to_mask value Convert @var{value} to a mask. @value{InternalUseOnly} @end defun @defun to_sequence value Convert @var{value} to a sequence. @value{InternalUseOnly} @end defun @defun to_string value @defunx str value Convert @var{value} to a character string. @end defun @defvar today Return today's date. @end defvar @smallexample @c command:F2FDF4B,with_input:3406FC1 $ ledger -f expr.dat --now 2015/01/01 --format "%(today)\n" reg assets @end smallexample @smallexample @c output:F2FDF4B 2015/01/01 @end smallexample @defun top_amount @value{FIXME:UNDOCUMENTED} @end defun @defun total_expr Return the calculated total of the posting according to the @option{--total} option. @end defun @defun trim value Trim leading and trailing whitespace from @var{value}. @smallexample @c command:377BBAB,with_input:3406FC1 $ ledger -f expr.dat --format "»%(trim(' Trimmed '))«\n" reg assets @end smallexample @smallexample @c output:377BBAB »Trimmed« @end smallexample @end defun @defun truncatedstring total_len account_len Truncate @var{string} to @var{total_len} ensuring that each account is at least @var{account_len} long. @end defun @defun unround @value{FIXME:UNDOCUMENTED} @end defun @defun unrounded @value{FIXME:UNDOCUMENTED} @end defun @defun value_date @value{FIXME:UNDOCUMENTED} @end defun @node Format Strings, Extending with Python, Value Expressions, Top @chapter Format Strings @menu * Format String Basics:: * Format String Structure:: * Format Expressions:: * Balance format:: * Formatting Functions and Codes:: @end menu @node Format String Basics, Format String Structure, Format Strings, Format Strings @section Format String Basics @findex --format @var{FORMAT_STRING} @findex --balance-format @var{FORMAT_STRING} @findex --budget-format @var{FORMAT_STRING} @findex --cleared-format @var{FORMAT_STRING} @findex --csv-format @var{FORMAT_STRING} @findex --plot-amount-format @var{FORMAT_STRING} @findex --plot-total-format @var{FORMAT_STRING} @findex --pricedb-format @var{FORMAT_STRING} @findex --prices-format @var{FORMAT_STRING} @findex --register-format @var{FORMAT_STRING} Format strings may be used to change the output format of reports. They are specified by passing a formatting string to the @option{--format @var{FORMAT_STRING} (-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 @option{--balance-format @var{FORMAT_STRING}} @item @option{--budget-format @var{FORMAT_STRING}} @item @option{--cleared-format @var{FORMAT_STRING}} @item @option{--csv-format @var{FORMAT_STRING}} @item @option{--plot-amount-format @var{FORMAT_STRING}} @item @option{--plot-total-format @var{FORMAT_STRING}} @item @option{--pricedb-format @var{FORMAT_STRING}} @item @option{--prices-format @var{FORMAT_STRING}} @item @option{--register-format @var{FORMAT_STRING}} @end itemize @node Format String Structure, Format Expressions, Format String Basics, Format Strings @section Format String Structure Within a format string, a substitution is specified using a percent @samp{%} character. 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 @samp{%}, 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: @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. @node Format Expressions, Balance format, Format String Structure, Format Strings @section Format Expressions @findex --amount @var{EXPR} @findex --total @var{VEXPR} For demonstration purposes the journal data from @ref{expr.dat} is used. The allowable expressions are: @table @code @item % Inserts a percent sign. @smallexample @c command:6F90EFC,with_input:3406FC1 $ ledger -f expr.dat --format "%%\n" reg assets @end smallexample @smallexample @c output:6F90EFC % @end smallexample @item t Inserts the results of the value expression specified by @option{--amount @var{EXPR} (-t)}. If @option{--amount @var{EXPR} (-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{--total @var{VEXPR} (-T)}. If @option{--total @var{VEXPR} (-T)} was not specified, the current report style's value expression is used. @item (EXPR) Inserts the amount resulting from the value expression given in parentheses. To insert five times the total value of an account, for example, one could say @samp{%12(5*O)}. Note: It's important to put the five first in that expression, so that the commodity doesn't get stripped from the total. @smallexample @c command:494256E,with_input:3406FC1 $ ledger -f expr.dat --format "%12(5*O)\n" reg assets @end smallexample @smallexample @c output:494256E ¤ -617,25 @end smallexample @item [DATEFMT] Inserts the result of formatting a posting's date with a date format string, exactly like those supported by @code{strftime (3)}. For example: @samp{%[%Y/%m/%d %H:%M:%S]}. @item S Insert the path name of the file from which the transaction's data was read. Only sensible in a @command{register} report. @c Note: Unable to test this properly since the output depends on @c where the ledger source tree resides in the filesystem. @smallexample $ ledger -f ~/journal.dat --format "%S\n" reg assets @end smallexample @smallexample /home/jwiegley/journal.dat @end smallexample @item B Inserts the beginning character position of that transaction within the file. @smallexample @c command:2B669C9,with_input:3406FC1 $ ledger -f expr.dat --format "%B\n" reg assets @end smallexample @smallexample @c output:2B669C9 26 @end smallexample @item b Inserts the beginning line of that transaction within the file. @smallexample @c command:F6E356F,with_input:3406FC1 $ ledger -f expr.dat --format "%b\n" reg assets @end smallexample @smallexample @c output:F6E356F 2 @end smallexample @item E Inserts the ending character position of that transaction within the file. @smallexample @c command:0E55246,with_input:3406FC1 $ ledger -f expr.dat --format "%E\n" reg assets @end smallexample @smallexample @c output:0E55246 90 @end smallexample @item e Inserts the ending line of that transaction within the file. @smallexample @c command:A26F4C0,with_input:3406FC1 $ ledger -f expr.dat --format "%e\n" reg assets @end smallexample @smallexample @c output:A26F4C0 3 @end smallexample @item D Returns the date according to the default format. @item d 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 returns a 1, otherwise returns 0. @item Y This is the same as @samp{%X}, except that it only displays a state character if all of the member postings have the same state. @item C Inserts the transaction code. This is the value specified between parentheses on the first line of the transaction. @smallexample @c command:C1CAAF3,with_input:3406FC1 $ ledger -f expr.dat --format "%C\n" reg assets @end smallexample @c Note: The output needs a space character at the end @c for this test to pass @smallexample @c output:C1CAAF3 (C0D3) @end smallexample @item P Inserts the payee related to a posting. @smallexample @c command:F41A9BB,with_input:3406FC1 $ ledger -f expr.dat --format "%P\n" reg assets @end smallexample @smallexample @c output:F41A9BB PiggyBank @end smallexample @c @item a @c Inserts the optimal short name for an account. This is normally @c used in balance reports. It prints a parent account's name if that @c name has not been printed yet, otherwise it just prints the @c account's name. @item A Inserts the full name of an account. @smallexample @c command:29A70DD,with_input:3406FC1 $ ledger -f expr.dat --format "%A\n" reg @end smallexample @smallexample @c output:29A70DD Assets:Cash Expenses:Office Supplies @end smallexample @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 @c all the same}, followed by the full account name. This is offered @c as a printing optimization, so that combined with @code{%Y}, only @c the minimum amount of state detail is printed. @c @item o @c Inserts the ``optimized'' form of a posting's amount. This is used @c by the print report. In some cases, this inserts nothing; in @c others, it inserts the posting amount and its cost. It's use is @c not recommended unless you are modifying the print report. @item N Inserts the note associated with a posting, if one exists. @smallexample @c command:E6DC93A,with_input:3406FC1 $ ledger -f expr.dat --format "%N\n" reg assets @end smallexample @smallexample @c output:E6DC93A Payee: PiggyBank @end smallexample @item / The @samp{%/} 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. @smallexample @c command:E80897D,with_input:3406FC1 $ ledger -f expr.dat --format "%P\n%/%A\n" reg @end smallexample @smallexample @c output:E80897D PiggyBank Expenses:Office Supplies @end smallexample @end table @node Balance format, Formatting Functions and Codes, Format Expressions, Format Strings @section Balance format @findex --balance-format @var{FORMAT_STRING} @findex --format @var{FORMAT_STRING} As an example of how flexible the @option{--format @var{FORMAT_STRING}} 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))" " %(!options.flat ? depth_spacer : \"\")" "%-(ansify_if(partial_account(options.flat), blue if color))\n%/" "%$1\n%/" "--------------------\n" @end smallexample @node Formatting Functions and Codes, , Balance format, Format Strings @section Formatting Functions and Codes @menu * Field Widths:: * Colors:: * Quantities and Calculations:: * Date Functions:: * Date and Time Format Codes:: * Text Formatting:: * Data File Parsing Information:: @end menu @node Field Widths, Colors, Formatting Functions and Codes, Formatting Functions and Codes @subsection Field Widths 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 itemize @node Colors, Quantities and Calculations, Field Widths, Formatting Functions and Codes @subsection Colors 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} @item @code{yellow} @tab @code{white} @tab @code{blink} @item @code{blue} @tab @code{black} @end multitable @node Quantities and Calculations, Date Functions, Colors, Formatting Functions and Codes @subsection Quantities and Calculations @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 Date Functions, Date and Time Format Codes, Quantities and Calculations, Formatting Functions and Codes @subsection Date Functions @findex --now @var{DATE} The following functions allow you to manipulate and format dates. @table @code @item date Return the date of the current transaction. @item format_date(date, "FORMAT_STRING") Format the date using the given format string. @item now Return the current date and time. If the @option{--now @var{DATE}} option is defined it will return that value. @item today Return the current date. If the @option{--now @var{DATE}} 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 @menu * Date and Time Format Codes:: @end menu @node Date and Time Format Codes, Text Formatting, Date Functions, Formatting Functions and 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. @menu * Days:: * Weekdays:: * Month:: * Miscellaneous Date Codes:: @end menu @node Days, Weekdays, Date and Time Format Codes, Date and Time Format Codes @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 @samp{20111214} which provides a date that is simple to sort on. @node Weekdays, Month, Days, Date and Time Format Codes @subsubsection Weekdays You can have additional weekday information in your date with @samp{%A} as @table @code @item %m-%d-%Y %A yields @samp{02-10-2010 Wednesday}. @item %A %m-%d-%Y yields @samp{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 up to 10. @item %e day of the month (dd), no leading zero up to 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 @node Month, Miscellaneous Date Codes, Weekdays, Date and Time Format Codes @subsubsection Month You can have additional month information in your date with @samp{%B} as @table @code @item %m-%d-%Y %B yields @samp{02-10-2010 February}. @item %B %m-%d-%Y yields @samp{February 02-10-2010}. @end table @noindent These are options you can select for month @table @code @item %m @samp{mm} month as two digits. @item %b Locale’s abbreviated month, for example @samp{02} might be abbreviated as @samp{Feb}. @item %B Locale’s full month, variable length, e.g. February. @end table @node Miscellaneous Date Codes, , Month, Date and Time Format Codes @subsubsection Miscellaneous Date Codes Additional date format parameters which can be used: @table @code @item %U week number Sunday as first day of week, ranging 01–53. @item %W week number Monday as first day of week, ranging 01–53. @item %V week of the year, ranging 01–53. @item %C century, ranging 00–99. @item %D yields @code{%m/%d/%y} as in @samp{02/10/10}. @item %x locale’s date representation, as @samp{02/10/2010} for the U.S. @item %F yields @code{%Y-%m-%d} as in @samp{2010-02-10}. @end table @node Text Formatting, Data File Parsing Information, Date and Time Format Codes, Formatting Functions and Codes @subsection Text Formatting 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 @code{color} on an TTY display. Has no effect if directed to a file. @item justify(value, first_width, latter_width, right_justify, colorize) Right or left justify the string representing @code{value}. The width of the field in the first line is given by @code{first_width}. For subsequent lines the width is given by @code{latter_width}. If @code{latter_width=-1}, then @code{first_width} is use for all lines. 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 @code{STR} with @samp{\n}. @item quoted(STR) Return @code{STR} surrounded by double quotes, @samp{"STR"}. @item strip(value) Values can have numerous annotations, such as effective dates and lot prices. @code{strip} removes these annotations. @end table @node Data File Parsing Information, , Text Formatting, Formatting Functions and Codes @subsection Data File Parsing Information The following format strings provide locational metadata regarding the coordinates of entries in the source data file(s) that generated the posting. @table @code @item filename the name of the ledger data file from whence the posting came, abbreviated @samp{S}. @item beg_pos character position in @code{filename} where entry for posting begins, abbreviated @samp{B}. @item end_pos character position in @code{filename} where entry for posting ends, abbreviated @samp{E}. @item beg_line line number in @code{filename} where entry for posting begins, abbreviated @samp{b}. @item end_line line number in @code{filename} where entry for posting ends, abbreviated @samp{e}. @end table @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 versus Cooked:: * Queries:: * Embedded Python:: * Amounts:: @end menu @node Basic data traversal, Raw versus Cooked, Extending with Python, Extending with Python @section Basic data traversal 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 Commodities that Amounts refer to. 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. Within the Journal live all the Transactions, Postings, and other objects related to your data. There are also AutomatedTransactions and PeriodicTransactions, etc. Here is how you would traverse all the postings in your data file: @smallexample import ledger 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 @node Raw versus Cooked, Queries, Basic data traversal, Extending with Python @section Raw versus Cooked 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: @smallexample @c input:validate = true (Assets:Cash) $100 2012-03-01 KFC Expenses:Food $100 Assets:Credit @end smallexample In this case, the @emph{raw} regular transaction in this file is: @smallexample @c input:validate 2012-03-01 KFC Expenses:Food $100 Assets:Credit @end smallexample While the @emph{cooked} form is: @smallexample @c input:validate 2012-03-01 KFC Expenses:Food $100 Assets:Credit $-100 (Assets:Cash) $100 @end smallexample So the easy way to think about raw vs. cooked is that raw is the unprocessed data, and cooked has had all considerations applied. When you traverse a Journal by iterating over 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: @smallexample for post in ledger.read_journal("sample.dat").query("food"): print "Transferring %s to/from %s" % (post.amount, post.account) @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 @code{xact} member: @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. @node Queries, Embedded Python, Raw versus Cooked, Extending with Python @section Queries The Journal.query() method accepts every argument you can specify on the command-line, including @option{--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. @node Embedded Python, Amounts, Queries, Extending with Python @section Embedded Python You can embed Python into your data files using the 'python' directive: @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)) tag PATH assert check_path(value) 2012-02-29 KFC ; PATH: somebogusfile.dat Expenses:Food $20 Assets:Cash @end smallexample Any Python functions you define this way become immediately available as valexpr functions. @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, Major Changes from version 2.6, Extending with Python, Top @chapter Ledger for Developers @menu * Internal Design:: * Journal File Format for Developers:: * Developer Commands:: * Ledger Development Environment:: @end menu @node Internal Design, Journal File Format for Developers, Ledger for Developers, Ledger for Developers @section Internal Design Ledger is developed as a tiered set of functionality, where lower tiers know nothing about the higher tiers. In fact, multiple libraries are built during the development the process, and link unit tests to these libraries, so that it is a link error for a lower tier to violate this modularity. Those tiers are: @itemize @item Utility code There's lots of general utility in Ledger for doing time parsing, using Boost.Regex, error handling, etc. It's all done in a way that can be reused in other projects as needed. @item Commoditized Amounts (amount_t, commodity_t and friends) A numerical abstraction combining multi-precision rational numbers (via GMP) with commodities. These structures can be manipulated like regular numbers in either C++ or Python (as Amount objects). @item Commodity Pool Commodities are all owned by a commodity pool, so that future parsing of amounts can link to the same commodity and established a consistent price history and record of formatting details. @item Balances Adds the concept of multiple amounts with varying commodities. Supports simple arithmetic, and multiplication and division with non-commoditized values. @item Price history Amounts have prices, and these are kept in a data graph which the amount code itself is only dimly aware of (there's three points of access so an amount can query its revalued price on a given date). @item Values Often the higher layers in Ledger don't care if something is an amount or a balance, they just want to add stuff to it or print it. For this, I created a type-erasure class, value_t/Value, into which many things can be stuffed and then operated on. They can contain amounts, balances, dates, 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 @code{amount_t} by a @code{balance_t}). This is the core data type for the value expression language. @item Value expressions The next layer up adds functions and operators around the Value concept. This lets you apply transformations and tests to Values at runtime without having to bake it into C++. The set of functions available is defined by each object type in Ledger (posts, accounts, transactions, etc.), though the core engine knows nothing about these. At its base, it only knows how to apply operators to values, and how to pass them to and receive them from functions. @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 functionality of their own, but are purely translated from the input string down to the corresponding value expression, for example the input string @samp{cash} is translated to @samp{(account =~ /cash/)}. This is a convenience layer. @item Format strings Format strings let you interpolate value expressions into strings, 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 @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. @item Journal items Next is a base type shared by anything that can appear in a journal: an item_t. It contains details common to all such parsed entities, like what file and line it was found on, etc. @item Journal posts The most numerous object found in a Journal, postings are a type of item that contain an account, an amount, a cost, and metadata. There are some other complications, like the account can be marked virtual, the amount could be an expression, etc. @item Journal transactions Postings are owned by transactions, always. This subclass of @code{item_t} knows about the date, the payee, etc. If a date or metadata tag is requested from a posting and it doesn't have that information, the transaction is queried to see if it can provide it. @item Journal accounts Postings are also shared by accounts, though the actual memory is managed by the transaction. Each account knows all the postings within it, but contains relatively little information of its own. @item The Journal object Finally, all transactions with their postings, and all accounts, are owned by a @code{journal_t} object. This is the go-to object for querying and reporting on your data. @item Textual journal parser There is a textual parser, wholly contained in @file{textual.cc}, which knows how 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 @file{iterators.h} and @file{iterators.cc}. This iteration logic is kept out of the basic journal objects themselves for the sake of modularity. @item Comparators Another abstraction isolated to its own layer, this class encapsulating the comparison of journal objects, based on whatever value expression the user passed to @option{--sort @var{VEXPR}}. @item Temporaries Many reports bring pseudo-journal objects into existence, like postings which report totals in a @samp{Total} account. These objects are created and managed by a @code{temporaries_t} object, which gets used in many places by the reporting filters. @item Option handling There is an option handling subsystem used by many of the layers further down. It makes it relatively easy for me to add new options, and to have those option settings immediately accessible to value expressions. @item Session objects Every journal object is owned by a session, with the session providing support for that object. In GUI terms, this is the Controller object for the journal Data object, where every document window would be a separate session. They are all owned by the global scope. @item Report objects Every time you create any report output, a report object is created to determine what you want to see. In the Ledger REPL, a new report object is created every time a command is executed. In CLI mode, only one report object ever comes into being, as Ledger immediately exits after displaying the results. @item Reporting filters The way Ledger generates data is this: it asks the session for the current journal, and then creates an iterator applied to that journal. The kind of 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 the iterator. There are many, many item handlers, which can be chained together. Each one receives an item (post, account, xact, etc.), performs some action on it, and then passes it down to the next handler in the chain. There 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 @file{filters.h}, there are 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 @file{chain.cc}. It took a really long time to get this logic exactly right, 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 called output modules. They are implemented just like a regular filter, but they don't have a ``next'' filter to pass the data 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. @item Select queries Select queries know a lot about everything, even though they implement their logic by implementing the user's query in terms of all the other features thus presented. Select queries have no functionality of their own, they are simple a shorthand to provide access to much of Ledger's functionality via a cleaner, more consistent syntax. @item The Global Scope There is a master object which owns every other objects, and this is Ledger's global scope. It creates the other objects, provides REPL behavior for the command-line utility, etc. In GUI terms, this is the Application object. @item The Main Driver 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 @option{--debug @var{CODE}}. @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 for Developers, Developer Commands, Internal Design, Ledger for Developers @section Journal File Format for Developers This chapter offers a complete description of the journal data format, suitable for implementers in other languages to follow. For users, the chapter on keeping a journal is less extensive, but more typical of common usage (@pxref{Keeping a Journal}). Data is collected in the form of @dfn{transactions} which occur in one or more @dfn{journal files}. Each transaction, in turn, is made up of one or more @dfn{postings}, which describe how @dfn{amounts} flow from one @dfn{account} to another. Here is an example of the simplest of journal files: @smallexample @c input:validate 2010/05/31 Just an example Expenses:Some:Account $100.00 Income:Another:Account @end smallexample In this example, there is a transaction date, a payee, or description of the transaction, and two postings. The postings show movement of one hundred dollars from an account within the Income hierarchy, to the specified expense account. The name and meaning of these accounts is arbitrary, with no preferences implied, although you will find it useful to follow standard accounting practices (@pxref{Principles of Accounting with Ledger}). Since an amount is missing from the second posting, it is assumed to 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 posting as the destination, and the final as the source. Thus, the amount of the first posting is typically positive. Consider: @smallexample @c input:validate 2010/05/31 An income transaction Assets:Checking $1,000.00 Income:Salary 2010/05/31 An expense transaction Expenses:Dining $100.00 Assets:Checking @end smallexample @menu * Comments and meta-data:: * Specifying Amounts:: * Posting costs:: * Primary commodities:: @end menu @node Comments and meta-data, Specifying Amounts, Journal File Format for Developers, Journal File Format for Developers @subsection Comments and meta-data Comments are generally started using a @samp{;}. 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: @samp{#}, @samp{|}, and @samp{*}. @node Specifying Amounts, Posting costs, Comments and meta-data, Journal File Format for Developers @subsection Specifying Amounts @cindex amounts The heart of a journal is the amounts it records, and this fact is reflected in the diversity of amount expressions allowed. All of them are covered here, though it must be said that sometimes, there are multiple ways to achieve a desired result. @emph{Note:} It is important to note that there must be at least two spaces between the end of the account and the beginning of the amount (including a commodity designator). @menu * Integer Amounts:: * Commoditized Amounts:: @end menu @node Integer Amounts, Commoditized Amounts, Specifying Amounts, Specifying Amounts @subsubsection Integer Amounts In the simplest form, bare decimal numbers are accepted: @smallexample @c input:validate 2010/05/31 An income transaction Assets:Checking 1000.00 Income:Salary @end smallexample @cindex uncommoditized amounts Such amounts may only use an optional period for a decimal point. These are referred to as @dfn{integer amounts} or @dfn{uncommoditized amounts}. In most ways they are similar to @dfn{commoditized amounts}, but for one significant difference: They always display in reports with @dfn{full precision}. More on this in a moment. For now, a word must be said about how Ledger stores numbers. Every number parsed by Ledger is stored internally as an infinite-precision rational value. Floating-point math is never used, as it cannot be trusted to maintain precision of values. So, in the case of @samp{1000.00} above, the internal value is @samp{100000/100}. While rational numbers are great at not losing precision, the question arises: How should they be displayed? A number like @samp{100000/100} is no problem, since it represents a clean decimal fraction. But what about when the number @samp{1/1} is divided by three? How should one print @samp{1/3}, an infinitely repeating decimal? Ledger gets around this problem by rendering rationals into decimal at the last possible moment, and only for display. As such, some rounding must, at times, occur. If this rounding would affect the calculation of a running total, special accommodation postings are generated to make you aware it has happened. In practice, it happens rarely, but even then it does not reflect adjustment of the @emph{internal amount}, only the displayed amount. What has still not been answered is how Ledger rounds values. Should @samp{1/3} be printed as @samp{0.33} or @samp{0.33333}? For commoditized amounts, the number of decimal places is decided by observing how each commodity is used; but in the case of integer amounts, an arbitrary factor must be chosen. Initially, this factor is six. Thus, @samp{1/3} is printed back as @samp{0.333333}. Further, this rounding factor becomes associated with each particular value, and is carried through mathematical operations. For example, if that particular number were multiplied by itself, the decimal precision of the result would be twelve. Addition and subtraction do not affect precision. Since each integer amount retains its own display precision, this is called @dfn{full precision}, as opposed to commoditized amounts, which always look to their commodity to know what precision they should round to, and so use @dfn{commodity precision}. @node Commoditized Amounts, , Integer Amounts, Specifying Amounts @subsubsection Commoditized Amounts A @dfn{commoditized amount} is an integer amount which has an associated commodity. This commodity can appear before or after the amount, and may or may not be separated from it by a space. Most characters are allowed in a commodity name, except for the following: @itemize @bullet @item Any kind of white-space @item Numerical digits @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 surrounded by double quotes, for example: @smallexample 100 "EUN+133" @end smallexample If a @dfn{quoted commodity} is found, it is displayed in quotes as well, to avoid any confusion as to which part is the amount, and which part is the commodity. Another feature of commoditized amounts is that they are reported back in the same form as parsed. If you specify dollar amounts using @samp{$100}, they will print the same; likewise with @samp{100 $} or @samp{$100.000}. You may even use decimal commas, such as @samp{$100,00}, or thousand-marks, as in @samp{$10,000.00}. These display characteristics become associated with the commodity, with the result being that all amounts of the same commodity are reported consistently. Where this is most noticeable is the @dfn{display precision}, which is determined by the most precise value seen for a given commodity---in most cases. Ledger makes a distinction between @dfn{observed amounts} and unobserved amounts. An observed amount is critiqued by Ledger to determine how amounts using that commodity should be displayed; unobserved amounts are significant in their value only---no matter how they are specified, it does not change how other amounts in that commodity will be displayed. An example of this is found in cost expressions, covered next. @node Posting costs, Primary commodities, Specifying Amounts, Journal File Format for Developers @subsection Posting costs You have seen how to specify either a commoditized or an integer amount for a posting. But what if the amount you paid for something was in one commodity, and the amount received was another? There are two main ways to express this: @smallexample @c input:validate 2010/05/31 Farmer's Market Assets:My Larder 100 apples Assets:Checking -$20.00 @end smallexample In this example, you have paid twenty dollars for one hundred apples. The cost to you is twenty cents per apple, and Ledger calculates this implied cost for you. You can also make the cost explicit using a @dfn{cost amount}: @smallexample @c input:validate 2010/05/31 Farmer's Market Assets:My Larder 100 apples @@ $0.200000 Assets:Checking @end smallexample Here the @dfn{per-unit cost} is given explicitly in the form of a cost amount; and since cost amounts are @emph{unobserved}, the use of six decimal places has no effect on how dollar amounts are displayed in the final report. You can also specify the @dfn{total cost}: @smallexample @c input:validate 2010/05/31 Farmer's Market Assets:My Larder 100 apples @@@@ $20 Assets:Checking @end smallexample These three forms have identical meaning. In most cases the first is preferred, but the second two are necessary when more than two postings are involved: @smallexample @c input:validate 2010/05/31 Farmer's Market Assets:My Larder 100 apples @@ $0.200000 Assets:My Larder 100 pineapples @@ $0.33 Assets:My Larder 100 "crab apples" @@ $0.04 Assets:Checking @end smallexample Here the implied cost is @samp{$57.00}, which is entered into the null posting automatically so that the transaction balances. @node Primary commodities, , Posting costs, Journal File Format for Developers @subsection Primary commodities @findex --market @findex --basis In every transaction involving more than one commodity, there is always one which is the @dfn{primary commodity}. This commodity should be thought of as the exchange commodity, or the commodity used to buy and sell units of the other commodity. In the fruit examples above, dollars are the primary commodity. This is decided by Ledger based on the placement of the commodity in the transaction: @smallexample @c input:validate 2010/05/31 Sample Transaction Expenses 100 secondary Assets -50 primary 2010/05/31 Sample Transaction Expenses 100 secondary @@ 0.5 primary Assets 2010/05/31 Sample Transaction Expenses 100 secondary @@@@ 50 primary Assets @end smallexample The only case where knowledge of primary versus secondary comes into play is in reports that use the @option{--market (-V)} or @option{--basis (-B)} options. With these, only primary commodities are shown. If a transaction uses only one commodity, this commodity is also considered a primary. In fact, when Ledger goes about ensuring that all transactions balance to zero, it only ever asks this of primary commodities. @node Developer Commands, Ledger Development Environment, Journal File Format for Developers, Ledger for Developers @section Developer Commands @menu * @command{echo}:: * @command{reload}:: * @command{source}:: * Debug Options:: * Pre-Commands:: @end menu @node @command{echo}, @command{reload}, Developer Commands, Developer Commands @subsection @command{echo} @findex echo This command simply echoes its argument back to the output. @node @command{reload}, @command{source}, @command{echo}, Developer Commands @subsection @command{reload} @findex 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 @command{source}, Debug Options, @command{reload}, Developer Commands @subsection @command{source} @findex source The @command{source} command takes 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, @command{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. @ftable @option @item --args-only Ignore init files and environment variables for the ledger run. @item --debug @var{CODE} If Ledger has been built with debug options this will provide extra data during the run. The following are the available @var{CODES} to debug: @multitable @columnfractions .32 .43 .27 @item @code{account.display} @tab @code{draft.xact} @tab @code{option.names} @item @code{account.sorted} @tab @code{expr.calc} @tab @code{org.next_amount} @item @code{amount.commodities} @tab @code{expr.compile} @tab @code{org.next_total} @item @code{amount.convert} @tab @code{expr.merged.compile} @tab @code{parser.error} @item @code{amount.is_zero} @tab @code{filters.changed_value} @tab @code{pool.commodities} @item @code{amount.parse} @tab @code{filters.changed_value.rounding} @tab @code{post.assign} @item @code{amount.price} @tab @code{filters.collapse} @tab @code{python.init} @item @code{amount.refs} @tab @code{filters.forecast} @tab @code{python.interp} @item @code{amount.roundto} @tab @code{filters.interval} @tab @code{query.mask} @item @code{amount.truncate} @tab @code{filters.revalued} @tab @code{report.predicate} @item @code{amount.unround} @tab @code{format.abbrev} @tab @code{scope.search} @item @code{annotate.less} @tab @code{format.expr} @tab @code{scope.symbols} @item @code{archive.journal} @tab @code{generate.post} @tab @code{select.parse} @item @code{auto.columns} @tab @code{generate.post.string} @tab @code{textual.include} @item @code{budget.generate} @tab @code{history.find} @tab @code{textual.parse} @item @code{commodity.annotated.strip} @tab @code{history.map} @tab @code{timelog} @item @code{commodity.annotations} @tab @code{item.meta} @tab @code{times.epoch} @item @code{commodity.compare} @tab @code{ledger.read} @tab @code{times.interval} @item @code{commodity.download} @tab @code{ledger.validate} @tab @code{times.parse} @item @code{commodity.exchange} @tab @code{lookup} @tab @code{value.sort} @item @code{commodity.price.find} @tab @code{lookup.account} @tab @code{value.storage.refcount} @item @code{commodity.prices.add} @tab @code{mask.match} @tab @code{xact.extend} @item @code{commodity.prices.find} @tab @code{memory.debug} @tab @code{xact.extend.cleared} @item @code{csv.mappings} @tab @code{op.memory} @tab @code{xact.extend.fail} @item @code{csv.parse} @tab @code{option.args} @tab @code{xact.finalize} @end multitable @ @item --trace @var{INT} Enable tracing. The @var{INT} 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 @itemx -v Print detailed information on the execution of Ledger. @item --verify Enable additional assertions during run-time. This causes a significant slowdown. When combined with @option{--debug @var{CODE}} ledger will produce memory trace information. @item --verify-memory Verify that every constructed object is properly destructed. This is for debugging purposes only. @item --version Print version information and exit. @end ftable @node Pre-Commands, , Debug Options, Developer Commands @subsection Pre-Commands @cindex pre-commands Pre-commands are useful when you aren't sure how a command or option will work. The difference between a pre-command and a regular command is that pre-commands ignore the journal data file completely, nor is the user's init file read. @ftable @command @item eval @var{VEXPR} Evaluate the given value expression against the model transaction. @item format @var{FORMAT_STRING} 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 @samp{GenerateTests} harness for development testing. @item parse @var{VEXPR} @itemx expr @var{VEXPR} Print details of how ledger uses the given value expression description and apply it against a model transaction. @item period @var{PERIOD_EXPRESSION} Evaluate the given period and report how Ledger interprets it: @smallexample @c command:51F6A2C $ ledger period "this year" --now 2011-01-01 @end smallexample @smallexample @c output:51F6A2C --- 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 @itemx args Evaluate the given arguments and report how Ledger interprets it against the following model transaction: @smallexample @c command:validate $ ledger query "/Book/" @end smallexample @smallexample --- 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 script @value{FIXME:UNDOCUMENTED} @item template Shows the insertion template that the @command{xact} sub-command generates. This is a debugging command. @end ftable @node Ledger Development Environment, , Developer Commands, Ledger for Developers @section Ledger Development Environment @menu * @file{acprep} build configuration tool:: * Testing Framework:: @end menu @node @file{acprep} build configuration tool, Testing Framework, Ledger Development Environment, Ledger Development Environment @subsection @file{acprep} build configuration tool @node Testing Framework, , @file{acprep} build configuration tool, Ledger Development Environment @subsection Testing Framework Ledger source ships with a fairly complete set of tests to verify that all is well, and no old errors have resurfaced. Tests are run individually with @file{ctest}. All tests can be run using @code{make check} or @code{ninja check} depending on which build tool you prefer. Once built, the ledger executable resides under the @file{build} subdirectory in the source tree. Tests are built and stored in the @file{test} subdirectory for the build. For example, @file{~/ledger/build/ledger/opt/test}. @menu * Running Tests:: * Writing Tests:: @end menu @node Running Tests, Writing Tests, Testing Framework, Testing Framework @subsubsection Running Tests The complete test suite 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 lasts 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 from the @file{test} subdirectory of the build location. To execute a single test use @code{ctest -V -R regex}, where the regex matches the name of the test you want to build. There are nearly 300 tests stored under the @file{test} subdirectory in the main 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 To write a new test first decide to which broad category the test belongs: baseline or regression. Depending on the category tests are named differently baseline tests are prefixed with their type, e.g. @samp{cmd} (@pxref{Baseline Test Types} for valid types), whereas regressions are either named after the bug id, e.g. @samp{1234.test} or uuid @samp{91416D62.test}. In case several test files belong to the same bug number the files by appending @code{_X} where @samp{X} is the number of the test, e.g. @samp{1234_1.test}, @samp{1234_2.test}. Baseline Test Types: @anchor{Baseline Test Types} @table @code @item cmd Ledger commands like @command{register} or @command{balance} @item dir Ledger directives like @code{account} or @code{alias} @item feat Ledger features such as balance assertions in journal file @item opt Ledger options such as @option{--period} or @option{--format} @end table A ledger test file contains three sections: @enumerate @item the journal data used for the test, this can be empty in certain scenarios @item the ledger command-line options used for the test @item the expected output @end enumerate Ledger has a special command directive for tests, everything between @code{test} and @code{end test} is treated like a comment, so every Ledger test is automatically a valid Ledger file. The test scripts take the remainder of the @code{test} line and use it as command-line arguments for ledger, the text enclosed in @code{test} and @code{end test} is expected output, for example: @smallexample @c input:validate ; This is the journal data year 2014 12/24 (C0d3) Santa Claus Assets:Bank ¤ -150,00 Expenses:Presents ; The following line specifies the ledger command-line options for this test and ; everything between the next line and `end test` specifies the expected output test reg --payee=code 14-Dec-24 C0d3 Assets:Bank ¤ -150,00 ¤ -150,00 14-Dec-24 C0d3 Expenses:Presents ¤ 150,00 0 end test @end smallexample When it is necessary to test for errors printed to @code{stderr} redirect the test output by adding @code{->} to the @code{test} line and match the expected error text in an @code{__ERROR__} section: @smallexample 2014/01/01 * Acme Corporation Assets:Bank:Checking ¤ 1.000,00 [Fund:Vacation] ¤ 300,00 [Fund:Studies] ¤ 600,00 Income:Salary ¤ -2.000,00 test reg -> __ERROR__ While parsing file "$FILE", line 5: While balancing transaction from "$FILE", lines 1-5: > 2014/01/01 * Acme Corporation > Assets:Bank:Checking ¤ 1.000,00 > [Fund:Vacation] ¤ 300,00 > [Fund:Studies] ¤ 600,00 > Income:Salary ¤ -2.000,00 Unbalanced remainder is: ¤ -100,00 Amount to balance against: ¤ 1.900,00 Error: Transaction does not balance end test @end smallexample A special @code{$FILE} variable can be used to match the journal filename used during the test. To add new tests to the test suite use the rebuild_cache option for the build tool you use, for example @code{make rebuild_cache}, now the new tests can be run as documented in @ref{Running Tests}. @node Major Changes from version 2.6, Example Journal File, Ledger for Developers, Top @chapter Major Changes from version 2.6 The following have been removed from Ledger 3.0: @itemize @item OFX support. @item GnuCash file import. @item The option @option{--performance (-g)}. @item The balance report now defaults to showing all relevant accounts. This is the opposite of 2.x. That is, @command{bal} in 3.0 does what @samp{-s bal} did in 2.x. To see 2.6 behavior, use @option{--collapse (-n)} option in 3.0, like @samp{bal -n}. The @option{--subtotal (-s)} option no longer has any effect on balance reports. @end itemize @noindent The following are deprecated in Ledger 3.0: @itemize @item Single character value expressions are deprecated and should be changed to the new value expressions available in 3.0 @item The following environment variables have been renamed in Ledger 3.0: @table @env @item LEDGER is now @env{LEDGER_FILE}, @item LEDGER_INIT is now @env{LEDGER_INIT_FILE}, @item PRICE_HIST is now @env{LEDGER_PRICE_DB}, @item PRICE_EXP is now @env{LEDGER_PRICE_EXP}. @end table @end itemize @node Example Journal File, Miscellaneous Notes, Major Changes from version 2.6, Top @appendix Example Journal File The following journal file is included with the source distribution of ledger. It is called @file{drewr3.dat} and exhibits many ledger features, include automatic and virtual transactions, @smallexample @c input:validate ; -*- ledger -*- = /^Income/ (Liabilities:Tithe) 0.12 ;~ Monthly ; Assets:Checking $500.00 ; Income:Salary ;~ Monthly ; Expenses:Food $100 ; Assets 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=2011/01/01 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: apply tag hastag: true apply tag nestedtag: true 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 end tag 2011/12/01 Sale Assets:Checking:Business $ 30.00 Income:Sales end tag @end smallexample @node Miscellaneous Notes, Concepts Index, Example Journal File, Top @appendix Miscellaneous Notes Various notes from the discussion list that I haven't incorporated in to the main body of the documentation. @menu * Cookbook:: @end menu @node Cookbook, , Miscellaneous Notes, Miscellaneous Notes @section Cookbook @menu * Invoking Ledger:: * Ledger Files:: @end menu @node Invoking Ledger, Ledger Files, Cookbook, Cookbook @subsection Invoking Ledger @smallexample @c command:validate $ ledger --group-by "tag('trip')" bal @end smallexample @c FIXME: The following example fails to validate due to: @c While applying is_realzero to : @c Error: Cannot determine if an uninitialized value is really zero @c @smallexample @c command:validate @c $ ledger reg --sort "tag('foo')" %foo @c @end smallexample @smallexample @c command:validate $ ledger cleared VWCU NFCU Tithe Misentry @end smallexample @smallexample @c command:validate $ ledger register Joint --uncleared @end smallexample @smallexample @c command:validate $ ledger register Checking --sort d -d 'd>[2011/04/01]' until 2011/05/25 @end smallexample @node Ledger Files, , Invoking Ledger, Cookbook @subsection Ledger Files @smallexample @c input:validate = /^Income:Taxable/ (Liabilities:Tithe Owed) -0.1 = /Noah/ (Liabilities:Tithe Owed) -0.1 = /Jonah/ (Liabilities:Tithe Owed) -0.1 = /Tithe/ (Liabilities:Tithe Owed) -1.0 @end smallexample @node Concepts Index, Commands & Options Index, Miscellaneous Notes, Top @unnumbered Concepts Index @printindex cp @node Commands & Options Index, , Concepts Index, Top @unnumbered Commands & Options Index @printindex fn @bye ���������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/doc/version.texi.in��������������������������������������������������������������0000664�0000000�0000000�00000000604�12660234410�0017506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@set Ledger_VERSION_MAJOR @Ledger_VERSION_MAJOR@ @set Ledger_VERSION_MINOR @Ledger_VERSION_MINOR@ @set Ledger_VERSION_PATCH @Ledger_VERSION_PATCH@ @set Ledger_VERSION_PRERELEASE @Ledger_VERSION_PRERELEASE@ @set Ledger_VERSION_DATE @Ledger_VERSION_DATE@ @set VERSION @value{Ledger_VERSION_MAJOR}.@value{Ledger_VERSION_MINOR}.@value{Ledger_VERSION_PATCH}@value{Ledger_VERSION_PRERELEASE} ����������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0014522�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/Makefile���������������������������������������������������������������������0000664�0000000�0000000�00000003200�12660234410�0016155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Uncomment these if you are on OS X and want to build universal libraries. # This is only important if you intend to produce a Ledger binary for # installation. STOW_ROOT = /usr/local/Cellar/boost PRODUCTS = $(HOME)/Products GCC_VERSION = 4.7 BOOST_VERSION = 1_52_0 CC = gcc-mp-$(GCC_VERSION) ifeq ($(CC),clang) CXX = clang++ LD = llvm-ld DIR_SUFFIX = clang OPTJ = else CXX = g++-mp-$(GCC_VERSION) LD = gcc-mp-$(GCC_VERSION) DIR_SUFFIX = gcc$(subst .,,$(GCC_VERSION)) OPTJ = #-j8 endif CFLAGS = $(CPPFLAGS) -g2 -ggdb LDFLAGS = -g2 -ggdb BOOST_SOURCE = boost-release ifeq ($(GCC_VERSION),4.7) BOOST_DEFINES = define=_GLIBCXX__PTHREADS=1 else BOOST_DEFINES = endif ifeq ($(CC),clang) BOOST_TOOLSET = clang else BOOST_TOOLSET = darwin 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_VERSION) BOOST_BUILD = $(PRODUCTS)/$(BOOST_DIR) 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 perl -i -pe 's/flags darwin\.compile OPTIONS : -no-cpp-precomp -gdwarf-2 (-fexceptions )?;/flags darwin\.compile OPTIONS : -gdwarf-2 \1;/;' $(BOOST_SOURCE)/tools/build/v2/tools/darwin.jam boost-build: prepare-boost (cd $(BOOST_SOURCE) && \ sh bootstrap.sh && \ ./b2 $(OPTJ) debug release --prefix=$(BOOST_STOW) \ --build-dir=$(BOOST_BUILD) $(BOOST_FLAGS) install) clean: -rm -fr $(BOOST_STOW) $(BOOST_BUILD) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/build-gcc.sh�����������������������������������������������������������������0000775�0000000�0000000�00000001234�12660234410�0016712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # This build script is for OS X Lion users who have compiled openmpi and # 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 cat > ~/user-config.jam <<EOF using clang-darwin : : "/usr/local/bin/clang++" : <cxxflags>-std=c++11 <include>/usr/local/include ; EOF # jww (2012-04-24): This is still linking against /usr/lib/libc++.1.dylib # instead of /usr/local/lib/libc++.1.dylib make "$@" OPTJ=-j20 \ BOOST_DEFINES="-sICU_PATH=/opt/local cxxflags=\"-I/opt/local/include\" linkflags=\"-L/opt/local/lib\"" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/build-icc.sh�����������������������������������������������������������������0000775�0000000�0000000�00000001426�12660234410�0016717�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # This build script is for OS X Lion users who have compiled openmpi and # 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 cat > ~/user-config.jam <<EOF using intel : : "/opt/intel/bin/icc" : ; 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=icc LD=icc CC=icc AR=xiar OPTJ=-j20 \ BOOST_TOOLSET=intel DIR_SUFFIX=intel \ BOOST_DEFINES="-sINTEL_PATH=/opt/intel/bin -sINTEL_VERSION=12 -sICU_PATH=/usr/local cxxflags=\"-I/usr/local/include -I/opt/local/include\" linkflags=\"-L/usr/local/lib -L/opt/local/lib\"" ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/build.sh���������������������������������������������������������������������0000775�0000000�0000000�00000001731�12660234410�0016162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # This build script is for OS X Lion users who have compiled openmpi and # 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 cat > ~/user-config.jam <<EOF 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 \ BOOST_TOOLSET=clang-darwin DIR_SUFFIX=clang31 \ 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++\"" ���������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0016023�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0016571�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/LICENSE����������������������������������������������������������0000664�0000000�0000000�00000002472�12660234410�0017603�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/buildrelease.pl��������������������������������������������������0000775�0000000�0000000�00000000616�12660234410�0021574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /usr/bin/perl $release_files = 'source/utf8.h source/utf8/core.h source/utf8/checked.h source/utf8/unchecked.h doc/utf8cpp.html doc/ReleaseNotes'; # First get the latest version `svn update`; # Then construct the name of the zip file $argc = @ARGV; if ($argc > 0) { $zip_name = $ARGV[0]; } else { $zip_name = "utf8"; } # Zip the files to an archive `zip $zip_name $release_files`; ������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/samples/���������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0020235�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/samples/Makefile�������������������������������������������������0000664�0000000�0000000�00000000173�12660234410�0021676�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������CC = g++ CFLAGS = -g -Wall -pedantic docsample: docsample.cpp ../source/utf8.h $(CC) $(CFLAGS) docsample.cpp -odocsample �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/samples/docsample.cpp��������������������������������������������0000664�0000000�0000000�00000003301�12660234410�0022705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "../source/utf8.h" #include <iostream> #include <fstream> #include <string> #include <vector> using namespace std; int main(int argc, char** argv) { if (argc != 2) { cout << "\nUsage: docsample filename\n"; return 0; } const char* test_file_path = argv[1]; // Open the test file (must be UTF-8 encoded) ifstream fs8(test_file_path); if (!fs8.is_open()) { cout << "Could not open " << test_file_path << endl; return 0; } unsigned line_count = 1; string line; // Play with all the lines in the file while (getline(fs8, line)) { // check for invalid utf-8 (for a simple yes/no check, there is also utf8::is_valid function) string::iterator end_it = utf8::find_invalid(line.begin(), line.end()); if (end_it != line.end()) { cout << "Invalid UTF-8 encoding detected at line " << line_count << "\n"; cout << "This part is fine: " << string(line.begin(), end_it) << "\n"; } // Get the line length (at least for the valid part) int length = utf8::distance(line.begin(), end_it); cout << "Length of line " << line_count << " is " << length << "\n"; // Convert it to utf-16 vector<unsigned short> utf16line; utf8::utf8to16(line.begin(), end_it, back_inserter(utf16line)); // And back to utf-8; string utf8line; utf8::utf16to8(utf16line.begin(), utf16line.end(), back_inserter(utf8line)); // Confirm that the conversion went OK: if (utf8line != string(line.begin(), end_it)) cout << "Error in UTF-16 conversion at line: " << line_count << "\n"; line_count++; } return 0; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/source/����������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0020071�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/source/utf8.h����������������������������������������������������0000664�0000000�0000000�00000003023�12660234410�0021126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include "utf8/checked.h" #include "utf8/unchecked.h" #endif // header guard �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/source/utf8/�����������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0020757�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/source/utf8/checked.h��������������������������������������������0000664�0000000�0000000�00000027614�12660234410�0022530�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include "core.h" #include <stdexcept> namespace utf8 { // Base for the exceptions that may be thrown from the library class exception : public ::std::exception { }; // Exceptions that may be thrown from the library functions. class invalid_code_point : public exception { uint32_t cp; public: invalid_code_point(uint32_t cp) : cp(cp) {} virtual const char* what() const throw() { return "Invalid code point"; } uint32_t code_point() const {return cp;} }; class invalid_utf8 : public exception { uint8_t u8; public: invalid_utf8 (uint8_t u) : u8(u) {} virtual const char* what() const throw() { return "Invalid UTF-8"; } uint8_t utf8_octet() const {return u8;} }; class invalid_utf16 : public exception { uint16_t u16; public: invalid_utf16 (uint16_t u) : u16(u) {} virtual const char* what() const throw() { return "Invalid UTF-16"; } uint16_t utf16_word() const {return u16;} }; class not_enough_room : public exception { public: virtual const char* what() const throw() { return "Not enough space"; } }; /// The library API - functions intended to be called by the users template <typename octet_iterator> octet_iterator append(uint32_t cp, octet_iterator result) { if (!utf8::internal::is_code_point_valid(cp)) throw invalid_code_point(cp); if (cp < 0x80) // one octet *(result++) = static_cast<uint8_t>(cp); else if (cp < 0x800) { // two octets *(result++) = static_cast<uint8_t>((cp >> 6) | 0xc0); *(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80); } else if (cp < 0x10000) { // three octets *(result++) = static_cast<uint8_t>((cp >> 12) | 0xe0); *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80); *(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80); } else { // four octets *(result++) = static_cast<uint8_t>((cp >> 18) | 0xf0); *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f) | 0x80); *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80); *(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80); } return result; } template <typename octet_iterator, typename output_iterator> output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) { while (start != end) { octet_iterator sequence_start = start; internal::utf_error err_code = utf8::internal::validate_next(start, end); switch (err_code) { case internal::UTF8_OK : for (octet_iterator it = sequence_start; it != start; ++it) *out++ = *it; break; case internal::NOT_ENOUGH_ROOM: throw not_enough_room(); case internal::INVALID_LEAD: out = utf8::append (replacement, out); ++start; break; case internal::INCOMPLETE_SEQUENCE: case internal::OVERLONG_SEQUENCE: case internal::INVALID_CODE_POINT: out = utf8::append (replacement, out); ++start; // just one replacement mark for the sequence while (start != end && utf8::internal::is_trail(*start)) ++start; break; } } return out; } template <typename octet_iterator, typename output_iterator> inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); return utf8::replace_invalid(start, end, out, replacement_marker); } template <typename octet_iterator> uint32_t next(octet_iterator& it, octet_iterator end) { uint32_t cp = 0; internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); switch (err_code) { case internal::UTF8_OK : break; case internal::NOT_ENOUGH_ROOM : throw not_enough_room(); case internal::INVALID_LEAD : case internal::INCOMPLETE_SEQUENCE : case internal::OVERLONG_SEQUENCE : throw invalid_utf8(*it); case internal::INVALID_CODE_POINT : throw invalid_code_point(cp); } return cp; } template <typename octet_iterator> uint32_t peek_next(octet_iterator it, octet_iterator end) { return utf8::next(it, end); } template <typename octet_iterator> uint32_t prior(octet_iterator& it, octet_iterator start) { // can't do much if it == start if (it == start) throw not_enough_room(); octet_iterator end = it; // Go back until we hit either a lead octet or start while (utf8::internal::is_trail(*(--it))) if (it == start) throw invalid_utf8(*it); // error - no lead byte in the sequence return utf8::peek_next(it, end); } /// Deprecated in versions that include "prior" template <typename octet_iterator> uint32_t previous(octet_iterator& it, octet_iterator pass_start) { octet_iterator end = it; while (utf8::internal::is_trail(*(--it))) if (it == pass_start) throw invalid_utf8(*it); // error - no lead byte in the sequence octet_iterator temp = it; return utf8::next(temp, end); } template <typename octet_iterator, typename distance_type> void advance (octet_iterator& it, distance_type n, octet_iterator end) { for (distance_type i = 0; i < n; ++i) utf8::next(it, end); } template <typename octet_iterator> typename std::iterator_traits<octet_iterator>::difference_type distance (octet_iterator first, octet_iterator last) { typename std::iterator_traits<octet_iterator>::difference_type dist; for (dist = 0; first < last; ++dist) utf8::next(first, last); return dist; } template <typename u16bit_iterator, typename octet_iterator> octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { uint32_t cp = utf8::internal::mask16(*start++); // Take care of surrogate pairs first if (utf8::internal::is_lead_surrogate(cp)) { if (start != end) { uint32_t trail_surrogate = utf8::internal::mask16(*start++); if (utf8::internal::is_trail_surrogate(trail_surrogate)) cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; else throw invalid_utf16(static_cast<uint16_t>(trail_surrogate)); } else throw invalid_utf16(static_cast<uint16_t>(cp)); } // Lone trail surrogate else if (utf8::internal::is_trail_surrogate(cp)) throw invalid_utf16(static_cast<uint16_t>(cp)); result = utf8::append(cp, result); } return result; } template <typename u16bit_iterator, typename octet_iterator> u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) { while (start != end) { uint32_t cp = utf8::next(start, end); if (cp > 0xffff) { //make a surrogate pair *result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET); *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); } else *result++ = static_cast<uint16_t>(cp); } return result; } template <typename octet_iterator, typename u32bit_iterator> octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) { while (start != end) result = utf8::append(*(start++), result); return result; } template <typename octet_iterator, typename u32bit_iterator> u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) { while (start != end) (*result++) = utf8::next(start, end); return result; } // The iterator class template <typename octet_iterator> class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { octet_iterator it; octet_iterator range_start; octet_iterator range_end; public: iterator () {} explicit iterator (const octet_iterator& octet_it, const octet_iterator& range_start, const octet_iterator& range_end) : it(octet_it), range_start(range_start), range_end(range_end) { if (it < range_start || it > range_end) throw std::out_of_range("Invalid utf-8 iterator position"); } // the default "big three" are OK octet_iterator base () const { return it; } uint32_t operator * () const { octet_iterator temp = it; return utf8::next(temp, range_end); } bool operator == (const iterator& rhs) const { if (range_start != rhs.range_start || range_end != rhs.range_end) throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); return (it == rhs.it); } bool operator != (const iterator& rhs) const { return !(operator == (rhs)); } iterator& operator ++ () { utf8::next(it, range_end); return *this; } iterator operator ++ (int) { iterator temp = *this; utf8::next(it, range_end); return temp; } iterator& operator -- () { utf8::prior(it, range_start); return *this; } iterator operator -- (int) { iterator temp = *this; utf8::prior(it, range_start); return temp; } }; // class iterator } // namespace utf8 #endif //header guard ��������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/source/utf8/core.h�����������������������������������������������0000664�0000000�0000000�00000024711�12660234410�0022065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include <iterator> namespace utf8 { // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers // You may need to change them to match your system. // These typedefs have the same names as ones from cstdint, or boost/cstdint typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; // Helper code - not intended to be directly called by the library users. May be changed at any time namespace internal { // Unicode constants // Leading (high) surrogates: 0xd800 - 0xdbff // Trailing (low) surrogates: 0xdc00 - 0xdfff const uint16_t LEAD_SURROGATE_MIN = 0xd800u; const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; // Maximum valid value for a Unicode code point const uint32_t CODE_POINT_MAX = 0x0010ffffu; template<typename octet_type> inline uint8_t mask8(octet_type oc) { return static_cast<uint8_t>(0xff & oc); } template<typename u16_type> inline uint16_t mask16(u16_type oc) { return static_cast<uint16_t>(0xffff & oc); } template<typename octet_type> inline bool is_trail(octet_type oc) { return ((utf8::internal::mask8(oc) >> 6) == 0x2); } template <typename u16> inline bool is_lead_surrogate(u16 cp) { return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); } template <typename u16> inline bool is_trail_surrogate(u16 cp) { return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); } template <typename u16> inline bool is_surrogate(u16 cp) { return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); } template <typename u32> inline bool is_code_point_valid(u32 cp) { return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); } template <typename octet_iterator> inline typename std::iterator_traits<octet_iterator>::difference_type sequence_length(octet_iterator lead_it) { uint8_t lead = utf8::internal::mask8(*lead_it); if (lead < 0x80) return 1; else if ((lead >> 5) == 0x6) return 2; else if ((lead >> 4) == 0xe) return 3; else if ((lead >> 3) == 0x1e) return 4; else return 0; } template <typename octet_difference_type> inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) { if (cp < 0x80) { if (length != 1) return true; } else if (cp < 0x800) { if (length != 2) return true; } else if (cp < 0x10000) { if (length != 3) return true; } return false; } enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; /// Helper for get_sequence_x template <typename octet_iterator> utf_error increase_safely(octet_iterator& it, octet_iterator end) { if (++it == end) return NOT_ENOUGH_ROOM; if (!utf8::internal::is_trail(*it)) return INCOMPLETE_SEQUENCE; return UTF8_OK; } #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} /// get_sequence_x functions decode utf-8 sequences of the length x template <typename octet_iterator> utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); return UTF8_OK; } template <typename octet_iterator> utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); return UTF8_OK; } template <typename octet_iterator> utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point += (*it) & 0x3f; return UTF8_OK; } template <typename octet_iterator> utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point += (*it) & 0x3f; return UTF8_OK; } #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR template <typename octet_iterator> utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) { // Save the original value of it so we can go back in case of failure // Of course, it does not make much sense with i.e. stream iterators octet_iterator original_it = it; uint32_t cp = 0; // Determine the sequence length based on the lead octet typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type; const octet_difference_type length = utf8::internal::sequence_length(it); // Get trail octets and calculate the code point utf_error err = UTF8_OK; switch (length) { case 0: return INVALID_LEAD; case 1: err = utf8::internal::get_sequence_1(it, end, cp); break; case 2: err = utf8::internal::get_sequence_2(it, end, cp); break; case 3: err = utf8::internal::get_sequence_3(it, end, cp); break; case 4: err = utf8::internal::get_sequence_4(it, end, cp); break; } if (err == UTF8_OK) { // Decoding succeeded. Now, security checks... if (utf8::internal::is_code_point_valid(cp)) { if (!utf8::internal::is_overlong_sequence(cp, length)){ // Passed! Return here. code_point = cp; ++it; return UTF8_OK; } else err = OVERLONG_SEQUENCE; } else err = INVALID_CODE_POINT; } // Failure branch - restore the original value of the iterator it = original_it; return err; } template <typename octet_iterator> inline utf_error validate_next(octet_iterator& it, octet_iterator end) { uint32_t ignored; return utf8::internal::validate_next(it, end, ignored); } } // namespace internal /// The library API - functions intended to be called by the users // Byte order mark const uint8_t bom[] = {0xef, 0xbb, 0xbf}; template <typename octet_iterator> octet_iterator find_invalid(octet_iterator start, octet_iterator end) { octet_iterator result = start; while (result != end) { utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); if (err_code != internal::UTF8_OK) return result; } return result; } template <typename octet_iterator> inline bool is_valid(octet_iterator start, octet_iterator end) { return (utf8::find_invalid(start, end) == end); } template <typename octet_iterator> inline bool starts_with_bom (octet_iterator it, octet_iterator end) { return ( ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) ); } //Deprecated in release 2.3 template <typename octet_iterator> inline bool is_bom (octet_iterator it) { return ( (utf8::internal::mask8(*it++)) == bom[0] && (utf8::internal::mask8(*it++)) == bom[1] && (utf8::internal::mask8(*it)) == bom[2] ); } } // namespace utf8 #endif // header guard �������������������������������������������������������ledger-3.1.1+dfsg1/lib/utfcpp/v2_0/source/utf8/unchecked.h������������������������������������������0000664�0000000�0000000�00000021313�12660234410�0023061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include "core.h" namespace utf8 { namespace unchecked { template <typename octet_iterator> octet_iterator append(uint32_t cp, octet_iterator result) { if (cp < 0x80) // one octet *(result++) = static_cast<uint8_t>(cp); else if (cp < 0x800) { // two octets *(result++) = static_cast<uint8_t>((cp >> 6) | 0xc0); *(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80); } else if (cp < 0x10000) { // three octets *(result++) = static_cast<uint8_t>((cp >> 12) | 0xe0); *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80); *(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80); } else { // four octets *(result++) = static_cast<uint8_t>((cp >> 18) | 0xf0); *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)| 0x80); *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80); *(result++) = static_cast<uint8_t>((cp & 0x3f) | 0x80); } return result; } template <typename octet_iterator> uint32_t next(octet_iterator& it) { uint32_t cp = utf8::internal::mask8(*it); typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it); switch (length) { case 1: break; case 2: it++; cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); break; case 3: ++it; cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); ++it; cp += (*it) & 0x3f; break; case 4: ++it; cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); ++it; cp += (utf8::internal::mask8(*it) << 6) & 0xfff; ++it; cp += (*it) & 0x3f; break; } ++it; return cp; } template <typename octet_iterator> uint32_t peek_next(octet_iterator it) { return utf8::unchecked::next(it); } template <typename octet_iterator> uint32_t prior(octet_iterator& it) { while (utf8::internal::is_trail(*(--it))) ; octet_iterator temp = it; return utf8::unchecked::next(temp); } // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) template <typename octet_iterator> inline uint32_t previous(octet_iterator& it) { return utf8::unchecked::prior(it); } template <typename octet_iterator, typename distance_type> void advance (octet_iterator& it, distance_type n) { for (distance_type i = 0; i < n; ++i) utf8::unchecked::next(it); } template <typename octet_iterator> typename std::iterator_traits<octet_iterator>::difference_type distance (octet_iterator first, octet_iterator last) { typename std::iterator_traits<octet_iterator>::difference_type dist; for (dist = 0; first < last; ++dist) utf8::unchecked::next(first); return dist; } template <typename u16bit_iterator, typename octet_iterator> octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { uint32_t cp = utf8::internal::mask16(*start++); // Take care of surrogate pairs first if (utf8::internal::is_lead_surrogate(cp)) { uint32_t trail_surrogate = utf8::internal::mask16(*start++); cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; } result = utf8::unchecked::append(cp, result); } return result; } template <typename u16bit_iterator, typename octet_iterator> u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) { while (start < end) { uint32_t cp = utf8::unchecked::next(start); if (cp > 0xffff) { //make a surrogate pair *result++ = static_cast<uint16_t>((cp >> 10) + internal::LEAD_OFFSET); *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); } else *result++ = static_cast<uint16_t>(cp); } return result; } template <typename octet_iterator, typename u32bit_iterator> octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) { while (start != end) result = utf8::unchecked::append(*(start++), result); return result; } template <typename octet_iterator, typename u32bit_iterator> u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) { while (start < end) (*result++) = utf8::unchecked::next(start); return result; } // The iterator class template <typename octet_iterator> class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { octet_iterator it; public: iterator () {} explicit iterator (const octet_iterator& octet_it): it(octet_it) {} // the default "big three" are OK octet_iterator base () const { return it; } uint32_t operator * () const { octet_iterator temp = it; return utf8::unchecked::next(temp); } bool operator == (const iterator& rhs) const { return (it == rhs.it); } bool operator != (const iterator& rhs) const { return !(operator == (rhs)); } iterator& operator ++ () { ::std::advance(it, utf8::internal::sequence_length(it)); return *this; } iterator operator ++ (int) { iterator temp = *this; ::std::advance(it, utf8::internal::sequence_length(it)); return temp; } iterator& operator -- () { utf8::unchecked::prior(it); return *this; } iterator operator -- (int) { iterator temp = *this; utf8::unchecked::prior(it); return temp; } }; // class iterator } // namespace utf8::unchecked } // namespace utf8 #endif // header guard ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0014723�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/CMakeLists.txt��������������������������������������������������������������0000664�0000000�0000000�00000003566�12660234410�0017475�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������set(EMACS_LISP_SOURCES ledger-commodities.el ledger-complete.el ledger-exec.el ledger-fontify.el ledger-fonts.el ledger-fontify.el ledger-init.el ledger-mode.el ledger-navigate.el ledger-occur.el ledger-post.el ledger-reconcile.el ledger-regex.el ledger-report.el ledger-schedule.el ledger-sort.el ledger-state.el ledger-test.el ledger-texi.el ledger-xact.el) set(EMACS_LISP_SOURCES_UNCOMPILABLE ledger-context.el) # find emacs and complain if not found find_program(EMACS_EXECUTABLE emacs) macro(add_emacs_lisp_target el) configure_file(${el} ${CMAKE_CURRENT_BINARY_DIR}/${el}) # add rule (i.e. command) how to generate the byte-compiled file add_custom_command( OUTPUT ${el}c COMMAND ${EMACS_EXECUTABLE} -L ${CMAKE_CURRENT_BINARY_DIR} -l ${CMAKE_CURRENT_BINARY_DIR}/ledger-regex.el -batch -f batch-byte-compile ${CMAKE_CURRENT_BINARY_DIR}/${el} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${el} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Creating byte-compiled Emacs lisp ${CMAKE_CURRENT_BINARY_DIR}/${el}c") endmacro(add_emacs_lisp_target el) if (EMACS_EXECUTABLE) # uncompilable .el files foreach(el ${EMACS_LISP_SOURCES_UNCOMPILABLE}) configure_file(${el} ${CMAKE_CURRENT_BINARY_DIR}/${el}) list(APPEND EMACS_LISP_UNCOMPILABLE ${CMAKE_CURRENT_BINARY_DIR}/${el}) endforeach() # compilable .el files foreach(el ${EMACS_LISP_SOURCES}) add_emacs_lisp_target(${el}) list(APPEND EMACS_LISP_BINARIES ${CMAKE_CURRENT_BINARY_DIR}/${el}c) endforeach() add_custom_target(emacs_lisp_byte_compile ALL DEPENDS ${EMACS_LISP_BINARIES}) # install the byte-compiled emacs-lisp sources install(FILES ${EMACS_LISP_SOURCES} ${EMACS_LISP_BINARIES} ${EMACS_LISP_UNCOMPILABLE} DESTINATION share/emacs/site-lisp/ledger-mode) endif() ### CMakeLists.txt ends here ������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-commodities.el�������������������������������������������������������0000664�0000000�0000000�00000014146�12660234410�0021027�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-commodities.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 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 'ledger-regex) (defcustom ledger-reconcile-default-commodity "$" "The default commodity for use in target calculations in ledger reconcile." :type 'string :group 'ledger-reconcile) (defun ledger-read-commodity-with-prompt (prompt) "Read commodity name after PROMPT. Default value is `ledger-reconcile-default-commodity'." (let* ((buffer (current-buffer)) (commodities (with-temp-buffer (ledger-exec-ledger buffer (current-buffer) "commodities") (split-string (buffer-string) "\n" t)))) (completing-read prompt commodities nil t nil nil ledger-reconcile-default-commodity))) (defun ledger-split-commodity-string (str) "Split a commoditized string, STR, into two parts. Returns a list with (value commodity)." (let ((number-regex (if (assoc "decimal-comma" ledger-environment-alist) ledger-amount-decimal-comma-regex ledger-amount-decimal-period-regex))) (if (> (length str) 0) (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 (ledger-string-to-number (delete-and-extract-region (match-beginning 0) (match-end 0))) 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 (ledger-string-to-number (delete-and-extract-region (match-beginning 0) (match-end 0))) (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." ; break any balances with multi commodities into a list (mapcar #'(lambda (st) (ledger-split-commodity-string st)) (split-string str "[\n\r]"))) (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-strip (str char) "Return STR with CHAR removed." (replace-regexp-in-string char "" str)) (defun ledger-string-to-number (str &optional decimal-comma) "improve builtin string-to-number by handling internationalization, and return nil if number can't be parsed" (let ((nstr (if (or decimal-comma (assoc "decimal-comma" ledger-environment-alist)) (ledger-strip str ".") (ledger-strip str ",")))) (while (string-match "," nstr) ;if there is a comma now, it is a thousands separator (setq nstr (replace-match "." nil nil nstr))) (string-to-number nstr))) (defun ledger-number-to-string (n &optional decimal-comma) "number-to-string that handles comma as decimal." (let ((str (number-to-string n))) (when (or decimal-comma (assoc "decimal-comma" ledger-environment-alist)) (while (string-match "\\." str) (setq str (replace-match "," nil nil str)))) str)) (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 ((str (ledger-number-to-string (car c1))) (commodity (cadr c1))) (if (> (length commodity) 1) (concat str " " commodity) (concat commodity " " str)))) (defun ledger-read-commodity-string (prompt) "Read an amount from mini-buffer using PROMPT." (let ((str (read-from-minibuffer (concat prompt " (" ledger-reconcile-default-commodity "): "))) comm) (if (and (> (length str) 0) (ledger-split-commodity-string str)) (progn (setq comm (ledger-split-commodity-string str)) (if (cadr comm) comm (list (car comm) ledger-reconcile-default-commodity)))))) (provide 'ledger-commodities) ;;; ledger-commodities.el ends here ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-complete.el����������������������������������������������������������0000664�0000000�0000000�00000023370�12660234410�0020322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-complete.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Functions providing payee and account auto complete. (require 'pcomplete) ;; In-place completion support ;;; Code: (defun ledger-parse-arguments () "Parse whitespace separated arguments in the current region." ;; 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) ;; to support end of line metadata (save-excursion (when (search-backward ";" (line-beginning-position) t) (setq begin (match-beginning 0)))) (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-payees-in-buffer () "Scan buffer and return list of all payees." (let ((origin (point)) payees-list) (save-excursion (goto-char (point-min)) (while (re-search-forward ledger-payee-any-status-regex nil t) ;; matches first line (unless (and (>= origin (match-beginning 0)) (< origin (match-end 0))) (setq payees-list (cons (match-string-no-properties 3) payees-list))))) ;; add the payee ;; to the list (pcomplete-uniqify-list (nreverse payees-list)))) (defun ledger-find-accounts-in-buffer () (interactive) (let ((origin (point)) accounts (account-tree (list t)) (account-elements nil) (seed-regex (ledger-account-any-status-with-seed-regex (regexp-quote (car pcomplete-args))))) (save-excursion (goto-char (point-min)) (dolist (account (delete-dups (progn (while (re-search-forward seed-regex nil t) (unless (between origin (match-beginning 0) (match-end 0)) (setq accounts (cons (match-string-no-properties 2) accounts)))) accounts))) (let ((root account-tree)) (setq account-elements (split-string account ":")) (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 () "Return a tree of all accounts in the buffer." (let* ((current (caar (ledger-parse-arguments))) (elements (and current (split-string current ":"))) (root (ledger-find-accounts-in-buffer)) (prefix nil)) (while (cdr elements) (let ((xact (assoc (car elements) root))) (if xact (setq prefix (concat prefix (and prefix ":") (car elements)) root (cdr xact)) (setq root nil elements nil))) (setq elements (cdr elements))) (setq root (delete (list (car elements) t) root)) (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)) 'transaction) (if (null current-prefix-arg) (delete (caar (ledger-parse-arguments)) (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-trim-trailing-whitespace (str) (replace-regexp-in-string "[ \t]*$" "" str)) (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 (ledger-trim-trailing-whitespace (caar (ledger-parse-arguments)))) (rest-of-name name) xacts) (save-excursion (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) ".*\\)" ) 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 rest-of-name ?\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)))))) (defcustom ledger-complete-ignore-case t "Non-nil means that ledger-complete-at-point will be case-insensitive" :type 'boolean :group 'ledger) (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") (let ((pcomplete-ignore-case ledger-complete-ignore-case)) (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-char (* -1 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 (completions (pcomplete-completions)) (result (pcomplete-do-complete pcomplete-stub completions)) (pcomplete-termination-string "")) (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 'ledger-complete) ;;; ledger-complete.el ends here ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-context.el�����������������������������������������������������������0000664�0000000�0000000�00000020411�12660234410�0020167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-context.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Provide facilities for reflection in ledger buffers ;;; Code: (eval-when-compile (require 'cl)) ;; ledger-*-string constants are assembled in the ;; `ledger-single-line-config' macro to form the regex and list of ;; elements (defconst ledger-indent-string "\\(^[ \t]+\\)") (defconst ledger-status-string "\\(* \\|! \\)?") (defconst ledger-account-string "[\\[(]?\\(.*?\\)[])]?") (defconst ledger-separator-string "\\(\\s-\\s-+\\)") (defconst ledger-amount-string "\\(-?[0-9]+\\(?:[\\.,][0-9]*\\)?\\)") (defconst ledger-comment-string "[ \t]*;[ \t]*\\(.*?\\)") (defconst ledger-nil-string "\\([ \t]\\)") (defconst ledger-commodity-string "\\(.+?\\)") (defconst ledger-date-string "^\\([0-9]\\{4\\}[/-][01]?[0-9][/-][0123]?[0-9]\\)") (defconst ledger-code-string "\\((.*)\\)?") (defconst ledger-payee-string "\\(.*\\)") (defun ledger-get-regex-str (name) "Get the ledger regex of type NAME." (symbol-value (intern (concat "ledger-" (symbol-name name) "-string")))) (defun ledger-line-regex (elements) "Get a regex to match ELEMENTS on a single line." (concat (apply 'concat (mapcar 'ledger-get-regex-str elements)) "[ \t]*$")) (defmacro ledger-single-line-config (&rest elements) "Take list of ELEMENTS and return regex and element list for use in context-at-point" `(list (ledger-line-regex (quote ,elements)) (quote ,elements))) (defconst ledger-line-config (list (list 'xact (list (ledger-single-line-config date nil status nil code nil payee nil comment) (ledger-single-line-config date nil status nil code nil payee) (ledger-single-line-config date nil status nil payee))) (list 'acct-transaction (list (ledger-single-line-config indent comment) (ledger-single-line-config indent status account separator commodity amount nil comment) (ledger-single-line-config indent status account separator commodity amount) (ledger-single-line-config indent status account separator amount nil commodity comment) (ledger-single-line-config indent status account separator amount nil commodity) (ledger-single-line-config indent status account separator amount) (ledger-single-line-config indent status account separator comment) (ledger-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-+\\)?[[(]?\\([^\\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 'ledger-context) ;;; ledger-context.el ends here �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-exec.el��������������������������������������������������������������0000664�0000000�0000000�00000010151�12660234410�0017427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-exec.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 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) (defcustom ledger-mode-should-check-version t "Should Ledger-mode verify that the executable is working?" :type 'boolean :group 'ledger-exec) (defcustom ledger-binary-path "ledger" "Path to the ledger executable." :type 'file :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) "Return t if the ledger output in LEDGER-OUTPUT-BUFFER is successful." (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 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))) (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 ledger-mode-should-check-version (if (setq ledger-works (ledger-version-greater-p ledger-version-needed)) (message "Good Ledger Version") (message "Bad Ledger Version")))) (provide 'ledger-exec) ;;; ledger-exec.el ends here �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-fontify.el�����������������������������������������������������������0000664�0000000�0000000�00000020533�12660234410�0020166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-fontify.el --- Provide custom fontification for ledger-mode ;; Copyright (C) 2014 Craig P. 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Font-lock-mode doesn't handle multiline syntax very well. This ;; code provides font lock that is sensitive to overall transaction ;; states ;;; Code: (require 'ledger-navigate) (require 'ledger-regex) (require 'ledger-state) (defcustom ledger-fontify-xact-state-overrides nil "If t the highlight entire xact with state." :type 'boolean :group 'ledger) (defun ledger-fontify-buffer-part (&optional beg end len) "Fontify buffer from BEG to END, length LEN." (save-excursion (unless beg (setq beg (point-min))) (unless end (setq end (point-max))) (goto-char beg) (beginning-of-line) (while (< (point) end) (cond ((or (looking-at ledger-xact-start-regex) (looking-at ledger-posting-regex) (looking-at ledger-recurring-line-regexp)) (ledger-fontify-xact-at (point))) ((looking-at ledger-directive-start-regex) (ledger-fontify-directive-at (point)))) (ledger-navigate-next-xact-or-directive)))) (defun ledger-fontify-xact-at (position) "Fontify the xact at POSITION." (interactive "d") (save-excursion (goto-char position) (let ((extents (ledger-navigate-find-element-extents position)) (state (ledger-transaction-state))) (if (and ledger-fontify-xact-state-overrides state) (cond ((eq state 'cleared) (ledger-fontify-set-face extents 'ledger-font-xact-cleared-face)) ((eq state 'pending) (ledger-fontify-set-face extents 'ledger-font-xact-pending-face))) (ledger-fontify-xact-by-line extents))))) (defun ledger-fontify-xact-by-line (extents) "Do line-by-line detailed fontification of xact in EXTENTS." (save-excursion (ledger-fontify-xact-start (car extents)) (while (< (point) (cadr extents)) (if (looking-at "[ \t]+;") (ledger-fontify-set-face (list (point) (progn (end-of-line) (point))) 'ledger-font-comment-face) (ledger-fontify-posting (point))) (forward-line)))) (defun ledger-fontify-xact-start (pos) "POS should be at the beginning of a line starting an xact. Fontify the first line of an xact" (goto-char pos) (let ((line-start (line-beginning-position))) (goto-char line-start) (re-search-forward "[ \t]") (ledger-fontify-set-face (list line-start (match-beginning 0)) 'ledger-font-posting-date-face) (goto-char line-start) (re-search-forward ledger-xact-after-date-regex) (let ((state (save-match-data (ledger-state-from-string (match-string 1))))) (ledger-fontify-set-face (list (match-beginning 3) (match-end 3)) (cond ((eq state 'pending) 'ledger-font-payee-pending-face) ((eq state 'cleared) 'ledger-font-payee-cleared-face) (t 'ledger-font-payee-uncleared-face)))) (when (match-beginning 4) (ledger-fontify-set-face (list (match-beginning 4) (match-end 4)) 'ledger-font-comment-face)) (forward-line))) (defun ledger-fontify-posting (pos) "Fontify the posting at POS." (let* ((state nil) (end-of-line-comment nil) (end (progn (end-of-line) (point))) (start (progn (beginning-of-line) (point)))) ;; Look for a posting status flag (set-match-data nil 'reseat) (re-search-forward " \\([*!]\\) " end t) (if (match-string 1) (setq state (ledger-state-from-string (match-string 1)))) (beginning-of-line) (re-search-forward "[[:graph:]]\\([ \t][ \t]\\)" end 'end) ;; find the end of the account, or end of line (when (<= (point) end) ;; we are still on the line (ledger-fontify-set-face (list start (point)) (cond ((eq state 'cleared) 'ledger-font-posting-account-cleared-face) ((eq state 'pending) 'ledger-font-posting-account-pending-face) (t 'ledger-font-posting-account-face))) (when (< (point) end) ;; there is still more to fontify (setq start (point)) ;; update start of next font region (setq end-of-line-comment (re-search-forward ";" end 'end)) ;; find the end of the line, or start of a comment (ledger-fontify-set-face (list start (point) ) (cond ((eq state 'cleared) 'ledger-font-posting-amount-cleared-face) ((eq state 'pending) 'ledger-font-posting-amount-pending-face) (t 'ledger-font-posting-amount-face))) (when end-of-line-comment (setq start (point)) (end-of-line) (ledger-fontify-set-face (list (- start 1) (point)) ;; subtract 1 from start because we passed the semi-colon 'ledger-font-comment-face)))))) (defun ledger-fontify-directive-at (pos) "Fontify the directive at POS." (let ((extents (ledger-navigate-find-element-extents pos)) (face 'ledger-font-default-face)) (cond ((looking-at "=") (setq face 'ledger-font-auto-xact-face)) ((looking-at "~") (setq face 'ledger-font-periodic-xact-face)) ((looking-at "[;#%|\\*]") (setq face 'ledger-font-comment-face)) ((looking-at "\\(year\\)\\|Y") (setq face 'ledger-font-year-directive-face)) ((looking-at "account") (setq face 'ledger-font-account-directive-face)) ((looking-at "apply") (setq face 'ledger-font-apply-directive-face)) ((looking-at "alias") (setq face 'ledger-font-alias-directive-face)) ((looking-at "assert") (setq face 'ledger-font-assert-directive-face)) ((looking-at "\\(bucket\\)\\|A") (setq face 'ledger-font-bucket-directive-face)) ((looking-at "capture") (setq face 'ledger-font-capture-directive-face)) ((looking-at "check") (setq face 'ledger-font-check-directive-face)) ((looking-at "commodity") (setq face 'ledger-font-commodity-directive-face)) ((looking-at "define") (setq face 'ledger-font-define-directive-face)) ((looking-at "end") (setq face 'ledger-font-end-directive-face)) ((looking-at "expr") (setq face 'ledger-font-expr-directive-face)) ((looking-at "fixed") (setq face 'ledger-font-fixed-directive-face)) ((looking-at "include") (setq face 'ledger-font-include-directive-face)) ((looking-at "payee") (setq face 'ledger-font-payee-directive-face)) ((looking-at "P") (setq face 'ledger-font-price-directive-face)) ((looking-at "tag") (setq face 'ledger-font-tag-directive-face))) (ledger-fontify-set-face extents face))) (defun ledger-fontify-set-face (extents face) "Set the text in EXTENTS to FACE." (put-text-property (car extents) (cadr extents) 'face face)) (provide 'ledger-fontify) ;;; ledger-fontify.el ends here ���������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-fonts.el�������������������������������������������������������������0000664�0000000�0000000�00000020712�12660234410�0017640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-fonts.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; All of the faces for ledger mode are defined here. ;;; Code: (require 'ledger-regex) (defgroup ledger-faces nil "Ledger mode highlighting" :group 'ledger) (defface ledger-font-default-face `((t :inherit default)) "Default face" :group 'ledger-faces) (defface ledger-font-auto-xact-face `((t :foreground "orange" :weight normal)) "Default face for automatic transactions" :group 'ledger-faces) (defface ledger-font-periodic-xact-face `((t :foreground "green" :weight normal)) "Default face for automatic transactions" :group 'ledger-faces) (defface ledger-font-xact-cleared-face `((t :foreground "#AAAAAA" :weight normal)) "Default face for cleared transaction" :group 'ledger-faces) (defface ledger-font-xact-pending-face `((t :foreground "#444444" :weight normal)) "Default face for pending transaction" :group 'ledger-faces) (defface ledger-font-xact-open-face `((t :foreground "#000000" :weight normal)) "Default face for transaction under point" :group 'ledger-faces) (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 :inherit ledger-font-other-face)) "Default face for cleared (*) payees" :group 'ledger-faces) (defface ledger-font-payee-pending-face `((t :foreground "#F24B61" :weight normal)) "Default face for pending (!) payees" :group 'ledger-faces) (defface ledger-font-xact-highlight-face `((t :inherit ledger-occur-xact-face)) "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" :weight normal)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-directive-face `((t :inherit font-lock-preprocessor-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-account-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-price-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-apply-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-alias-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-assert-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-bucket-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-capture-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-check-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-commodity-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-define-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-end-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-expr-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-fixed-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-include-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-payee-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-tag-directive-face `((t :inherit ledger-font-directive-face)) "Default face for other transactions" :group 'ledger-faces) (defface ledger-font-year-directive-face `((t :inherit ledger-font-directive-face)) "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 :inherit ledger-font-other-face)) "Face for Ledger accounts" :group 'ledger-faces) (defface ledger-font-posting-amount-cleared-face `((t :inherit ledger-font-posting-account-cleared-face)) "Face for Ledger accounts" :group 'ledger-faces) (defface ledger-font-posting-account-pending-face `((t :inherit ledger-font-pending-face)) "Face for Ledger accounts" :group 'ledger-faces) (defface ledger-font-posting-amount-pending-face `((t :inherit ledger-font-posting-account-pending-face)) "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-font-posting-date-face `((t :foreground "#cb4b16" )) "Face for Ledger dates" :group 'ledger-faces) (defface ledger-occur-narrowed-face `((t :inherit font-lock-comment-face :invisible t)) "Default face for Ledger occur mode hidden transactions" :group 'ledger-faces) (defface ledger-occur-xact-face `((t :inherit highlight)) "Default face for Ledger occur mode shown transactions" :group 'ledger-faces) (defface ledger-font-comment-face `((t :inherit font-lock-comment-face)) "Face for Ledger comments" :group 'ledger-faces) (defface ledger-font-reconciler-uncleared-face `((t :inherit ledger-font-payee-uncleared-face)) "Default face for uncleared transactions in the reconcile window" :group 'ledger-faces) (defface ledger-font-reconciler-cleared-face `((t :inherit ledger-font-other-face)) "Default face for cleared (*) transactions in the reconcile window" :group 'ledger-faces) (defface ledger-font-reconciler-pending-face `((t :inherit ledger-font-pending-face)) "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 `(("account" . ledger-font-account-directive-face) ("apply" . ledger-font-apply-directive-face) ("alias" . ledger-font-alias-directive-face) ("assert" . ledger-font-assert-directive-face) ("bucket" . ledger-font-bucket-directive-face) ("capture" . ledger-font-capture-directive-face) ("check" . ledger-font-check-directive-face) ("commodity" . ledger-font-commodity-directive-face) ("define" . ledger-font-define-directive-face) ("end" . ledger-font-end-directive-face) ("expr" . ledger-font-expr-directive-face) ("fixed" . ledger-font-fixed-directive-face) ("include" . ledger-font-include-directive-face) ("payee" . ledger-font-payee-directive-face) ("tag" . ledger-font-tag-directive-face) ("year" . ledger-font-year-directive-face)) "Expressions to highlight in Ledger mode.") (provide 'ledger-fonts) ;;; ledger-fonts.el ends here ������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-init.el��������������������������������������������������������������0000664�0000000�0000000�00000005734�12660234410�0017461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-init.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Determine the ledger environment (require 'ledger-regex) ;;; Code: (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) (defvar ledger-default-date-format "%Y/%m/%d") (defun ledger-init-parse-initialization (buffer) "Parse the .ledgerrc file in 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 () "Load and parse the .ledgerrc 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 (setq ledger-environment-alist (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 'ledger-init) ;;; ledger-init.el ends here ������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-mode.el��������������������������������������������������������������0000664�0000000�0000000�00000035142�12660234410�0017436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-mode.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Most of the general ledger-mode code is here. ;;; Code: (require 'ledger-regex) (require 'cus-edit) (require 'esh-util) (require 'esh-arg) (require 'easymenu) (require 'ledger-commodities) (require 'ledger-complete) (require 'ledger-context) (require 'ledger-exec) (require 'ledger-fonts) (require 'ledger-fontify) (require 'ledger-init) (require 'ledger-navigate) (require 'ledger-occur) (require 'ledger-post) (require 'ledger-reconcile) (require 'ledger-report) (require 'ledger-sort) (require 'ledger-state) (require 'ledger-test) (require 'ledger-texi) (require 'ledger-xact) (require 'ledger-schedule) ;;; Code: (defgroup ledger nil "Interface to the Ledger command-line accounting program." :group 'data) (defconst ledger-version "3.0" "The version of ledger.el currently loaded.") (defconst ledger-mode-version "3.0.0") (defun ledger-mode-dump-variable (var) "Format VAR for dump to buffer." (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." (interactive) (find-file "ledger-mode-dump") (ledger-mode-dump-group 'ledger)) (defun ledger-current-year () "The default current year for adding transactions." (format-time-string "%Y")) (defun 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) "Read an account from the minibuffer with PROMPT." (let ((context (ledger-context-at-point))) (ledger-read-string-with-default prompt (if (eq (ledger-context-current-field context) 'account) (regexp-quote (ledger-context-field-value context 'account)) nil)))) (defun ledger-read-date (prompt) "Return user-supplied date after `PROMPT', defaults to today." (let* ((default (ledger-year-and-month)) (date (read-string prompt default 'ledger-minibuffer-history))) (if (or (string= date default) (string= "" date)) (format-time-string (or (cdr (assoc "date-format" ledger-environment-alist)) ledger-default-date-format)) date))) (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 (&optional arg) "Display the cleared-or-pending balance. And calculate the target-delta of the account being reconciled. With prefix argument \\[universal-argument] ask for the target commodity and convert the balance into that." (interactive "P") (let* ((account (ledger-read-account-with-prompt "Account balance to show")) (target-commodity (when arg (ledger-read-commodity-with-prompt "Target commodity: "))) (buffer (current-buffer)) (balance (with-temp-buffer (apply 'ledger-exec-ledger buffer (current-buffer) "cleared" account (when target-commodity (list "-X" target-commodity))) (if (> (buffer-size) 0) (buffer-substring-no-properties (point-min) (1- (point-max))) (concat account " is empty."))))) (when balance (message balance)))) (defun ledger-display-ledger-stats () "Display the cleared-or-pending balance. And calculate the target-delta of the account being reconciled." (interactive) (let* ((buffer (current-buffer)) (balance (with-temp-buffer (ledger-exec-ledger buffer (current-buffer) "stats") (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>, INTERACTIVELY. Can indent, complete or align depending on context." (interactive "p") (if (= (point) (line-beginning-position)) (indent-to ledger-post-account-alignment-column) (if (and (> (point) 1) (looking-back "\\([^ \t]\\)" 1)) (ledger-pcomplete interactively) (ledger-post-align-postings (line-beginning-position) (line-end-position))))) (defvar ledger-mode-abbrev-table) (defvar ledger-date-string-today (format-time-string (or (cdr (assoc "date-format" ledger-environment-alist)) ledger-default-date-format))) (defun ledger-remove-effective-date () "Remove the effective date from a transaction or posting." (interactive) (let ((context (car (ledger-context-at-point)))) (save-excursion (save-restriction (narrow-to-region (point-at-bol) (point-at-eol)) (beginning-of-line) (cond ((eq 'xact context) (re-search-forward ledger-iso-date-regexp) (when (= (char-after) ?=) (let ((eq-pos (point))) (delete-region eq-pos (re-search-forward ledger-iso-date-regexp))))) ((eq 'acct-transaction context) ;; Match "; [=date]" & delete string (when (re-search-forward (concat ledger-comment-regex "\\[=" ledger-iso-date-regexp "\\]") nil 'noerr) (replace-match "")))))))) (defun ledger-insert-effective-date (&optional date) "Insert effective date `DATE' to the transaction or posting. If `DATE' is nil, prompt the user a date. Replace the current effective date if there's one in the same line. With a prefix argument, remove the effective date." (interactive) (if (and (listp current-prefix-arg) (= 4 (prefix-numeric-value current-prefix-arg))) (ledger-remove-effective-date) (let* ((context (car (ledger-context-at-point))) (date-string (or date (ledger-read-date "Effective date: ")))) (save-restriction (narrow-to-region (point-at-bol) (point-at-eol)) (cond ((eq 'xact context) (beginning-of-line) (re-search-forward ledger-iso-date-regexp) (when (= (char-after) ?=) (ledger-remove-effective-date)) (insert "=" date-string)) ((eq 'acct-transaction context) (end-of-line) (ledger-remove-effective-date) (insert " ; [=" date-string "]"))))))) (defun ledger-mode-remove-extra-lines () "Get rid of multiple empty lines." (goto-char (point-min)) (while (re-search-forward "\n\n\\(\n\\)+" nil t) (replace-match "\n\n"))) (defun ledger-mode-clean-buffer () "Indent, remove multiple line feeds and sort the buffer." (interactive) (let ((start (point-min-marker)) (end (point-max-marker))) (goto-char start) (ledger-navigate-beginning-of-xact) (beginning-of-line) (let ((target (buffer-substring (point) (progn (end-of-line) (point))))) (untabify start end) (ledger-sort-buffer) (ledger-post-align-postings start end) (ledger-mode-remove-extra-lines) (goto-char start) (search-forward target)))) (defvar ledger-mode-syntax-table (let ((table (make-syntax-table text-mode-syntax-table))) (modify-syntax-entry ?\; "<" table) (modify-syntax-entry ?\n ">" table) table) "Syntax table in use in `ledger-mode' buffers.") (defvar ledger-mode-map (let ((map (make-sparse-keymap))) (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 [(control ?c) (control ?l)] 'ledger-display-ledger-stats) (define-key map [(control ?c) (control ?q)] 'ledger-post-align-xact) (define-key map [tab] 'ledger-magic-tab) (define-key map [(control tab)] 'ledger-post-align-xact) (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-navigate-prev-xact-or-directive) (define-key map [(meta ?n)] 'ledger-navigate-next-xact-or-directive) (define-key map [(meta ?q)] 'ledger-post-align-dwim) map) "Keymap for `ledger-mode'.") (easy-menu-define ledger-mode-menu ledger-mode-map "Ledger menu" '("Ledger" ["Narrow to REGEX" ledger-occur] ["Show all transactions" ledger-occur-mode ledger-occur-mode] ["Ledger Statistics" ledger-display-ledger-stats ledger-works] "---" ["Show upcoming transactions" ledger-schedule-upcoming] ["Add Transaction (ledger xact)" ledger-add-transaction ledger-works] ["Complete Transaction" ledger-fully-complete-xact] ["Delete Transaction" ledger-delete-current-transaction] "---" ["Calc on Amount" ledger-post-edit-amount] "---" ["Check Balance" ledger-display-balance-at-point ledger-works] ["Reconcile Account" ledger-reconcile ledger-works] "---" ["Toggle Current Transaction" ledger-toggle-current-transaction] ["Toggle Current Posting" ledger-toggle-current] ["Copy Trans at Point" ledger-copy-transaction-at-point] "---" ["Clean-up Buffer" ledger-mode-clean-buffer] ["Align Region" ledger-post-align-postings mark-active] ["Align Xact" ledger-post-align-xact] ["Sort Region" ledger-sort-region mark-active] ["Sort Buffer" ledger-sort-buffer] ["Mark Sort Beginning" ledger-sort-insert-start-mark] ["Mark Sort End" ledger-sort-insert-end-mark] ["Set effective date" ledger-insert-effective-date] "---" ["Customize Ledger Mode" (lambda () (interactive) (customize-group 'ledger))] ["Set Year" ledger-set-year ledger-works] ["Set Month" ledger-set-month ledger-works] "---" ["Run Report" ledger-report ledger-works] ["Goto Report" ledger-report-goto ledger-works] ["Re-run Report" ledger-report-redo ledger-works] ["Save Report" ledger-report-save ledger-works] ["Edit Report" ledger-report-edit ledger-works] ["Kill Report" ledger-report-kill ledger-works])) ;;;###autoload (define-derived-mode ledger-mode text-mode "Ledger" "A mode for editing ledger data files." (ledger-check-version) (when (boundp 'font-lock-defaults) (setq font-lock-defaults '(ledger-font-lock-keywords t t nil nil (font-lock-fontify-region-function . ledger-fontify-buffer-part)))) (set (make-local-variable 'pcomplete-parse-arguments-function) 'ledger-parse-arguments) (set (make-local-variable 'pcomplete-command-completion-function) 'ledger-complete-at-point) (add-hook 'completion-at-point-functions 'pcomplete-completions-at-point nil t) (add-hook 'after-save-hook 'ledger-report-redo) (add-hook 'post-command-hook 'ledger-highlight-xact-under-point nil t) (ledger-init-load-init-file) (setq comment-start ";") (set (make-local-variable 'indent-region-function) 'ledger-post-align-postings)) (defun ledger-set-year (newyear) "Set ledger's idea of the current year to the prefix argument NEWYEAR." (interactive "p") (setq ledger-year (if (= newyear 1) (read-string "Year: " (ledger-current-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") (setq ledger-month (if (= newmonth 1) (read-string "Month: " (ledger-current-month)) (format "%02d" newmonth)))) (provide 'ledger-mode) ;;; ledger-mode.el ends here ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-navigate.el����������������������������������������������������������0000664�0000000�0000000�00000013256�12660234410�0020312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-navigate.el --- Provide navigation services through the ledger buffer. ;; Copyright (C) 2014-2015 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; ;;; Code: (require 'ledger-regex) (require 'ledger-context) (defun ledger-navigate-next-xact () "Move point to beginning of next xact." ;; make sure we actually move to the next xact, even if we are the ;; beginning of one now. (if (looking-at ledger-payee-any-status-regex) (forward-line)) (if (re-search-forward ledger-payee-any-status-regex nil t) (goto-char (match-beginning 0)) (goto-char (point-max)))) (defun ledger-navigate-start-xact-or-directive-p () "Return t if at the beginning of an empty or all-whitespace line." (not (looking-at "[ \t]\\|\\(^$\\)"))) (defun ledger-navigate-next-xact-or-directive () "Move to the beginning of the next xact or directive." (interactive) (beginning-of-line) (if (ledger-navigate-start-xact-or-directive-p) ; if we are the start of an xact, move forward to the next xact (progn (forward-line) (if (not (ledger-navigate-start-xact-or-directive-p)) ; we have moved forward and are not at another xact, recurse forward (ledger-navigate-next-xact-or-directive))) (while (not (or (eobp) ; we didn't start off at the beginning of an xact (ledger-navigate-start-xact-or-directive-p))) (forward-line)))) (defun ledger-navigate-prev-xact-or-directive () "Move point to beginning of previous xact." (interactive) (let ((context (car (ledger-context-at-point)))) (when (equal context 'acct-transaction) (ledger-navigate-beginning-of-xact)) (beginning-of-line) (re-search-backward "^[[:graph:]]" nil t))) (defun ledger-navigate-beginning-of-xact () "Move point to the beginning of the current xact." (interactive) ;; need to start at the beginning of a line incase we are in the first line of an xact already. (beginning-of-line) (let ((sreg (concat "^\\(=\\|~\\|" ledger-iso-date-regexp "\\)"))) (unless (looking-at sreg) (re-search-backward sreg nil t) (beginning-of-line))) (point)) (defun ledger-navigate-end-of-xact () "Move point to end of xact." (interactive) (ledger-navigate-next-xact-or-directive) (re-search-backward ".$") (end-of-line) (point)) (defun ledger-navigate-to-line (line-number) "Rapidly move point to line LINE-NUMBER." (goto-char (point-min)) (forward-line (1- line-number))) (defun ledger-navigate-find-xact-extents (pos) "Return list containing point for beginning and end of xact containing POS. Requires empty line separating xacts." (interactive "d") (save-excursion (goto-char pos) (list (ledger-navigate-beginning-of-xact) (ledger-navigate-end-of-xact)))) (defun ledger-navigate-find-directive-extents (pos) "Return the extents of the directive at POS." (goto-char pos) (let ((begin (progn (beginning-of-line) (point))) (end (progn (end-of-line) (+ 1 (point))))) ;; handle block comments here (beginning-of-line) (if (looking-at " *;") (progn (while (and (looking-at " *;") (> (point) (point-min))) (forward-line -1)) ;; We are either at the beginning of the buffer, or we found ;; a line outside the comment. If we are not at the ;; beginning of the buffer then we need to move forward a ;; line. (if (> (point) (point-min)) (progn (forward-line 1) (beginning-of-line))) (setq begin (point)) (goto-char pos) (beginning-of-line) (while (and (looking-at " *;") (< (point) (point-max))) (forward-line 1)) (setq end (point)))) (list begin end))) (defun ledger-navigate-block-comment (pos) "Move past the block comment at POS, and return its extents." (interactive "d") (goto-char pos) (let ((begin (progn (beginning-of-line) (point))) (end (progn (end-of-line) (point)))) ;; handle block comments here (beginning-of-line) (if (looking-at " *;") (progn (while (and (looking-at " *;") (> (point) (point-min))) (forward-line -1)) (setq begin (point)) (goto-char pos) (beginning-of-line) (while (and (looking-at " *;") (< (point) (point-max))) (forward-line 1)) (setq end (point)))) (list begin end))) (defun ledger-navigate-find-element-extents (pos) "Return list containing beginning and end of the entity surrounding POS." (interactive "d") (save-excursion (goto-char pos) (beginning-of-line) (if (looking-at "[ =~0-9\\[]") (ledger-navigate-find-xact-extents pos) (ledger-navigate-find-directive-extents pos)))) (provide 'ledger-navigate) ;;; ledger-navigate.el ends here ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-occur.el�������������������������������������������������������������0000664�0000000�0000000�00000014410�12660234410�0017620�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-occur.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 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: (require 'cl) (require 'ledger-navigate) (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-history nil "History of previously searched expressions for the prompt.") (defvar ledger-occur-current-regex nil "Pattern currently applied to narrow the buffer.") (make-variable-buffer-local 'ledger-occur-current-regex) (defvar ledger-occur-mode-map (make-sparse-keymap)) (define-minor-mode ledger-occur-mode "A minor mode which display only transactions matching `ledger-occur-current-regex'." nil (:eval (format " Ledger-Narrow(%s)" ledger-occur-current-regex)) ledger-occur-mode-map (if (and ledger-occur-current-regex ledger-occur-mode) (ledger-occur-refresh) (ledger-occur-remove-overlays) (message "Showing all transactions"))) (define-key ledger-occur-mode-map (kbd "C-c C-g") 'ledger-occur-refresh) (define-key ledger-occur-mode-map (kbd "C-c C-f") 'ledger-occur-mode) (defun ledger-occur-refresh () "Re-apply the current narrowing expression." (interactive) (let ((matches (ledger-occur-compress-matches (ledger-occur-find-matches ledger-occur-current-regex)))) (if matches (ledger-occur-create-overlays matches) (message "No matches found for '%s'" ledger-occur-current-regex) (ledger-occur-mode -1)))) (defun ledger-occur (regex) "Show only transactions in the current buffer which match REGEX. This command hides all xact in the current buffer except those matching REGEX. If REGEX is nil or empty, turn off any narrowing currently active." (interactive (list (read-regexp "Regexp" (ledger-occur-prompt) 'ledger-occur-history))) (if (or (null regex) (zerop (length regex))) ; empty regex, or already have narrowed, clear narrowing (ledger-occur-mode -1) (setq ledger-occur-current-regex regex) (ledger-occur-mode 1))) (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" (if (use-region-p) (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))) (defun ledger-occur-make-visible-overlay (beg end) (let ((ovl (make-overlay beg end (current-buffer)))) (overlay-put ovl ledger-occur-overlay-property-name t) (overlay-put ovl 'face 'ledger-occur-xact-face))) (defun ledger-occur-make-invisible-overlay (beg end) (let ((ovl (make-overlay beg end (current-buffer)))) (overlay-put ovl ledger-occur-overlay-property-name t) (overlay-put ovl 'invisible t))) (defun ledger-occur-create-overlays (ovl-bounds) "Create the overlays for the visible transactions. Argument OVL-BOUNDS contains bounds for the transactions to be left visible." (let* ((beg (caar ovl-bounds)) (end (cadar ovl-bounds))) (ledger-occur-remove-overlays) (ledger-occur-make-invisible-overlay (point-min) (1- beg)) (dolist (visible (cdr ovl-bounds)) (ledger-occur-make-visible-overlay beg end) (ledger-occur-make-invisible-overlay (1+ end) (1- (car visible))) (setq beg (car visible)) (setq end (cadr visible))) (ledger-occur-make-invisible-overlay (1+ end) (point-max)))) (defun ledger-occur-remove-overlays () "Remove the transaction hiding overlays." (interactive) (remove-overlays (point-min) (point-max) ledger-occur-overlay-property-name t)) (defun ledger-occur-find-matches (regex) "Return a list of 2-number tuples describing the beginning and end of transactions meeting REGEX." (save-excursion (goto-char (point-min)) ;; Set initial values for variables (let (endpoint lines bounds) ;; Search loop (while (not (eobp)) ;; if something found (when (setq endpoint (re-search-forward regex nil 'end)) (setq bounds (ledger-navigate-find-element-extents endpoint)) (push bounds lines) ;; move to the end of the xact, no need to search inside it more (goto-char (cadr bounds)))) (nreverse lines)))) (defun ledger-occur-compress-matches (buffer-matches) "identify sequential xacts to reduce number of overlays required" (if buffer-matches (let ((points (list)) (current-beginning (caar buffer-matches)) (current-end (cadar buffer-matches))) (dolist (match (cdr buffer-matches)) (if (< (- (car match) current-end) 2) (setq current-end (cadr match)) (push (list current-beginning current-end) points) (setq current-beginning (car match)) (setq current-end (cadr match)))) (nreverse (push (list current-beginning current-end) points))))) (provide 'ledger-occur) ;;; ledger-occur.el ends here ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-post.el��������������������������������������������������������������0000664�0000000�0000000�00000020055�12660234410�0017474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-post.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Utility functions for dealing with postings. (require 'ledger-regex) ;;; Code: (defgroup ledger-post nil "Options for controlling how Ledger-mode deals with postings and completion" :group 'ledger) (defcustom ledger-post-account-alignment-column 4 "The column Ledger-mode attempts to align accounts to." :type 'integer :group 'ledger-post) (defcustom ledger-post-amount-alignment-column 52 "The column Ledger-mode attempts to align amounts to." :type 'integer :group 'ledger-post) (defcustom ledger-post-amount-alignment-at :end "Position at which the amount is ailgned. Can be :end to align on the last number of the amount (can be followed by unaligned commodity) or :decimal to align at the decimal separator." :type '(radio (const :tag "align at the end of amount" :end) (const :tag "align at the decimal separator" :decimal)) :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) (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. PROMPT is a string to prompt with. CHOICES is a list of strings to choose from." (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)))) (defun 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) (let ((case-fold-search nil)) (when (re-search-forward ledger-amount-regex end t) (goto-char (match-beginning 0)) (skip-syntax-forward " ") (cond ((eq ledger-post-amount-alignment-at :end) (- (or (match-end 4) (match-end 3)) (point))) ((eq ledger-post-amount-alignment-at :decimal) (- (match-end 3) (point))))))) (defun ledger-next-account (&optional end) "Move to the beginning of the posting, or status marker, limit to 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-xact (pos) "Align all the posting in the xact at POS." (interactive "d") (let ((bounds (ledger-navigate-find-xact-extents pos))) (ledger-post-align-postings (car bounds) (cadr bounds)))) (defun ledger-post-align-postings (beg end) "Align all accounts and amounts between BEG and END, or the current region, or, if no region, the current line." (interactive "r") (save-excursion (let ((inhibit-modification-hooks t) acct-start-column acct-end-column acct-adjust amt-width amt-adjust (lines-left 1)) ;; Extend region to whole lines (let ((start-marker (set-marker (make-marker) (save-excursion (goto-char beg) (line-beginning-position)))) (end-marker (set-marker (make-marker) (save-excursion (goto-char end) (line-end-position))))) (untabify start-marker end-marker) (goto-char start-marker) ;; 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-marker)) (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-align-dwim () "Align all the posting of the current xact or the current region. If the point is in a comment, fill the comment paragraph as regular text." (interactive) (cond ((nth 4 (syntax-ppss)) (call-interactively 'ledger-post-align-postings) (fill-paragraph)) ((use-region-p) (call-interactively 'ledger-post-align-postings)) (t (call-interactively 'ledger-post-align-xact)))) (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)) ;; 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-string (match-string 0))) (goto-char (match-beginning 0)) (delete-region (match-beginning 0) (match-end 0)) (calc) (calc-eval val-string '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)))))) (provide 'ledger-post) ;;; ledger-post.el ends here �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-reconcile.el���������������������������������������������������������0000664�0000000�0000000�00000060635�12660234410�0020462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-reconcile.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;; Reconcile mode ;;; Commentary: ;; Code to handle reconciling Ledger files wiht outside sources ;;; Code: (require 'easymenu) (require 'ledger-init) (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 buffer." :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 transaction in the reconcile buffer. 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 t, 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 ledger-default-date-format "Date format for the reconcile buffer. Default is ledger-default-date-format." :type 'string :group 'ledger-reconcile) (defcustom ledger-reconcile-target-prompt-string "Target amount for reconciliation " "Prompt for recon target." :type 'string :group 'ledger-reconcile) (defcustom ledger-reconcile-buffer-header "Reconciling account %s\n\n" "Default header string for the reconcile buffer. If non-nil, the name of the account being reconciled will be substituted into the '%s'. If nil, no header will be displayed." :type 'string :group 'ledger-reconcile) (defcustom ledger-reconcile-buffer-line-format "%(date)s %-4(code)s %-50(payee)s %-30(account)s %15(amount)s\n" "Format string for the ledger reconcile posting format. Available fields are date, status, code, payee, account, amount. The format for each field is %WIDTH(FIELD), WIDTH can be preced by a minus sign which mean to left justify and pad the field. WIDTH is the minimum number of characters to display; if string is longer, it is not truncated unless ledger-reconcile-buffer-payee-max-chars or ledger-reconcile-buffer-account-max-chars is defined." :type 'string :group 'ledger-reconcile) (defcustom ledger-reconcile-buffer-payee-max-chars -1 "If positive, truncate payee name right side to max number of characters." :type 'integer :group 'ledger-reconcile) (defcustom ledger-reconcile-buffer-account-max-chars -1 "If positive, truncate account name left side to max number of characters." :type 'integer :group 'ledger-reconcile) (defcustom ledger-reconcile-sort-key "(0)" "Key for sorting reconcile buffer. Possible values are '(date)', '(amount)', '(payee)' or '(0)' for no sorting, i.e. using ledger file order." :type 'string :group 'ledger-reconcile) (defcustom ledger-reconcile-insert-effective-date nil "If t, prompt for effective date when clearing transactions during reconciliation." :type 'boolean :group 'ledger-reconcile) (defcustom ledger-reconcile-finish-force-quit nil "If t, will force closing reconcile window after \\[ledger-reconcile-finish]." :type 'boolean :group 'ledger-reconcile) ;; s-functions below are copied from Magnars' s.el ;; prefix ledger-reconcile- is added to not conflict with s.el (defun ledger-reconcile-s-pad-left (len padding s) "If S is shorter than LEN, pad it with PADDING on the left." (let ((extra (max 0 (- len (length s))))) (concat (make-string extra (string-to-char padding)) s))) (defun ledger-reconcile-s-pad-right (len padding s) "If S is shorter than LEN, pad it with PADDING on the right." (let ((extra (max 0 (- len (length s))))) (concat s (make-string extra (string-to-char padding))))) (defun ledger-reconcile-s-left (len s) "Return up to the LEN first chars of S." (if (> (length s) len) (substring s 0 len) s)) (defun ledger-reconcile-s-right (len s) "Return up to the LEN last chars of S." (let ((l (length s))) (if (> l len) (substring s (- l len) l) s))) (defun ledger-reconcile-truncate-right (str len) "Truncate STR right side with max LEN characters, and pad with '…' if truncated." (if (and (>= len 0) (> (length str) len)) (ledger-reconcile-s-pad-right len "…" (ledger-reconcile-s-left (- len 1) str)) str)) (defun ledger-reconcile-truncate-left (str len) "Truncate STR left side with max LEN characters, and pad with '…' if truncated." (if (and (>= len 0) (> (length str) len)) (ledger-reconcile-s-pad-left len "…" (ledger-reconcile-s-right (- len 1) str)) str)) (defun ledger-reconcile-get-cleared-or-pending-balance (buffer account) "Use BUFFER to 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" "%(scrub(display_total))" account) (ledger-split-commodity-string (buffer-substring-no-properties (point-min) (point-max)))))) (defun ledger-display-balance () "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 "Cleared and 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 ledger-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)) (inhibit-read-only t) status) (when (ledger-reconcile-get-buffer where) (with-current-buffer (ledger-reconcile-get-buffer where) (ledger-navigate-to-line (cdr where)) (forward-char) (setq status (ledger-toggle-current (if ledger-reconcile-toggle-to-pending 'pending 'cleared))) (when ledger-reconcile-insert-effective-date ;; Ask for effective date & insert it (ledger-insert-effective-date))) ;; 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)) (erase-buffer) (prog1 (ledger-do-reconcile ledger-reconcile-sort-key) (set-buffer-modified-p t)))) (defun ledger-reconcile-refresh-after-save () "Refresh the recon-window after the ledger buffer is saved." (let ((curbufwin (get-buffer-window (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)) (when curbufwin (select-window curbufwin) (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-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 (ledger-reconcile-get-buffer where) (with-current-buffer (ledger-reconcile-get-buffer where) (ledger-navigate-to-line (cdr where)) (ledger-delete-current-transaction (point))) (let ((inhibit-read-only t)) (goto-char (line-beginning-position)) (delete-region (point) (1+ (line-end-position))) (set-buffer-modified-p t)) (ledger-reconcile-refresh)))) (defun ledger-reconcile-visit (&optional come-back) "Recenter ledger buffer on transaction and COME-BACK if non-nil." (interactive) (beginning-of-line) (let* ((where (get-text-property (1+ (point)) 'where)) (target-buffer (if where (ledger-reconcile-get-buffer where) nil)) (cur-win (get-buffer-window (get-buffer ledger-recon-buffer-name)))) (when target-buffer (switch-to-buffer-other-window target-buffer) (ledger-navigate-to-line (cdr where)) (forward-char) (recenter) (ledger-highlight-xact-under-point) (forward-char -1) (when (and come-back cur-win) (select-window cur-win) (get-buffer ledger-recon-buffer-name))))) (defun ledger-reconcile-save () "Save the ledger buffer." (interactive) (let ((cur-buf (current-buffer)) (cur-point (point))) (dolist (buf (cons ledger-buf ledger-bufs)) (with-current-buffer buf (basic-save-buffer))) (switch-to-buffer-other-window cur-buf) (goto-char cur-point))) (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 if `ledger-reconcile-finish-force-quit'" (interactive) (save-excursion (goto-char (point-min)) (while (not (eobp)) (let ((where (get-text-property (point) 'where)) (face (get-text-property (point) 'face))) (if (eq face 'ledger-font-reconciler-pending-face) (with-current-buffer (ledger-reconcile-get-buffer where) (ledger-navigate-to-line (cdr where)) (ledger-toggle-current 'cleared)))) (forward-line 1))) (ledger-reconcile-save) (when ledger-reconcile-finish-force-quit (ledger-reconcile-quit))) (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-mode -1) (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 (ledger-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-reconcile-compile-format-string (fstr) "Return a function that implements the format string in FSTR." (let (fields (start 0)) (while (string-match "(\\(.*?\\))" fstr start) (setq fields (cons (intern (match-string 1 fstr)) fields)) (setq start (match-end 0))) (setq fields (list* 'format (replace-regexp-in-string "(.*?)" "" fstr) (nreverse fields))) `(lambda (date code status payee account amount) ,fields))) (defun ledger-reconcile-format-posting (beg where fmt date code status payee account amount) "Format posting for the reconcile buffer." (insert (funcall fmt date code status payee account amount)) ; Set face depending on cleared status (if status (if (eq status '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)))) (defun ledger-reconcile-format-xact (xact fmt) "Format XACT using FMT." (let ((date-format (or (cdr (assoc "date-format" ledger-environment-alist)) ledger-default-date-format))) (dolist (posting (nthcdr 5 xact)) (let ((beg (point)) (where (ledger-marker-where-xact-is xact posting))) (ledger-reconcile-format-posting beg where fmt (format-time-string date-format (nth 2 xact)) ; date (if (nth 3 xact) (nth 3 xact) "") ; code (nth 3 posting) ; status (ledger-reconcile-truncate-right (nth 4 xact) ; payee ledger-reconcile-buffer-payee-max-chars) (ledger-reconcile-truncate-left (nth 1 posting) ; account ledger-reconcile-buffer-account-max-chars) (nth 2 posting)))))) ; amount (defun ledger-do-reconcile (&optional sort) "SORT the uncleared transactions in the account and display them in the *Reconcile* buffer. Return a count of the uncleared transactions." (let* ((buf ledger-buf) (account ledger-acct) (ledger-success nil) (sort-by (if sort sort "(date)")) (xacts (with-temp-buffer (when (ledger-exec-ledger buf (current-buffer) "--uncleared" "--real" "emacs" "--sort" sort-by account) (setq ledger-success t) (goto-char (point-min)) (unless (eobp) (if (looking-at "(") (read (current-buffer))))))) ;current-buffer is the *temp* created above (fmt (ledger-reconcile-compile-format-string ledger-reconcile-buffer-line-format))) (if (and ledger-success (> (length xacts) 0)) (progn (insert (format ledger-reconcile-buffer-header account)) (dolist (xact xacts) (ledger-reconcile-format-xact xact fmt)) (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) (setq buffer-read-only t) (ledger-reconcile-ensure-xacts-visible) (length xacts))) (defun ledger-reconcile-ensure-xacts-visible () "Ensure 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 ledger-buf (add-hook 'kill-buffer-hook 'ledger-reconcile-quit nil t) (if (get-buffer-window ledger-buf) (select-window (get-buffer-window ledger-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-check-valid-account (account) "Check to see if ACCOUNT exists in the ledger file" (if (> (length account) 0) (save-excursion (goto-char (point-min)) (search-forward account nil t)))) (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))) (when (ledger-reconcile-check-valid-account account) (add-hook 'after-save-hook 'ledger-reconcile-refresh-after-save nil t) (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) (setq 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. (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 account))) (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))) (defmacro ledger-reconcile-change-sort-key-and-refresh (sort-by) "Set the sort-key to SORT-BY." `(lambda () (interactive) (setq ledger-reconcile-sort-key ,sort-by) (ledger-reconcile-refresh))) (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 ?x) (control ?s)] 'ledger-reconcile-save) (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 [(control ?c) (control ?o)] (ledger-reconcile-change-sort-key-and-refresh "(0)")) (define-key map [(control ?c) (control ?a)] (ledger-reconcile-change-sort-key-and-refresh "(amount)")) (define-key map [(control ?c) (control ?d)] (ledger-reconcile-change-sort-key-and-refresh "(date)")) (define-key map [(control ?c) (control ?p)] (ledger-reconcile-change-sort-key-and-refresh "(payee)")) map) "Keymap for `ledger-reconcile-mode'.") (easy-menu-define ledger-reconcile-mode-menu ledger-reconcile-mode-map "Ledger reconcile menu" `("Reconcile" ["Save" ledger-reconcile-save] ["Refresh" ledger-reconcile-refresh] ["Finish" ledger-reconcile-finish] "---" ["Reconcile New Account" ledger-reconcile] "---" ["Change Target Balance" ledger-reconcile-change-target] ["Show Cleared Balance" ledger-display-balance] "---" ["Sort by payee" ,(ledger-reconcile-change-sort-key-and-refresh "(payee)")] ["Sort by date" ,(ledger-reconcile-change-sort-key-and-refresh "(date)")] ["Sort by amount" ,(ledger-reconcile-change-sort-key-and-refresh "(amount)")] ["Sort by file order" ,(ledger-reconcile-change-sort-key-and-refresh "(0)")] "---" ["Toggle Entry" ledger-reconcile-toggle] ["Add Entry" ledger-reconcile-add] ["Delete Entry" ledger-reconcile-delete] "---" ["Next Entry" next-line] ["Visit Source" ledger-reconcile-visit] ["Previous Entry" previous-line] "---" ["Quit" ledger-reconcile-quit] )) (define-derived-mode ledger-reconcile-mode text-mode "Reconcile" "A mode for reconciling ledger entries.") (provide 'ledger-reconcile) ;;; ledger-reconcile.el ends here ���������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-regex.el�������������������������������������������������������������0000664�0000000�0000000�00000034557�12660234410�0017635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-regex.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. (require 'rx) (eval-when-compile (require 'cl)) (defconst ledger-amount-regex (concat "\\( \\|\t\\| \t\\)[ \t]*-?" "\\([A-Z$€£₹_(]+ *\\)?" ;; We either match just a number after the commodity with no ;; decimal or thousand separators or a number with thousand ;; separators. If we have a decimal part starting with `,' ;; or `.', because the match is non-greedy, it must leave at ;; least one of those symbols for the following capture ;; group, which then finishes the decimal part. "\\(-?\\(?:[0-9]+\\|[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 "^[;#|\\*%].*\\|[ \t]+;.*") (defconst ledger-multiline-comment-start-regex "^!comment$") (defconst ledger-multiline-comment-end-regex "^!end_comment$") (defconst ledger-multiline-comment-regex "^!comment\n\\(.*\n\\)*?!end_comment$") (defconst ledger-payee-any-status-regex "^[0-9]+[-/][-/.=0-9]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+\\(.+?\\)\\s-*\\(;\\|$\\)") (defconst ledger-payee-pending-regex "^[0-9]+[-/][-/.=0-9]+\\s-\\!\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\s-*\\(;\\|$\\)") (defconst ledger-payee-cleared-regex "^[0-9]+[-/][-/.=0-9]+\\s-\\*\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\s-*\\(;\\|$\\)") (defconst ledger-payee-uncleared-regex "^[0-9]+[-/][-/.=0-9]+\\s-+\\(([^)]+)\\s-+\\)?\\([^*].+?\\)\\s-*\\(;\\|$\\)") (defconst ledger-init-string-regex "^--.+?\\($\\|[ ]\\)") (defconst ledger-account-any-status-regex "^[ \t]+\\([*!]\\s-+\\)?\\([[(]?.+?\\)\\(\t\\|\n\\| [ \t]\\)") (defun ledger-account-any-status-with-seed-regex (seed) (concat "^[ \t]+\\([*!]\\s-+\\)?\\([[(]?" seed ".+?\\)\\(\t\\|\n\\| [ \t]\\)")) (defconst ledger-account-pending-regex "\\(^[ \t]+\\)\\(!\\s-*.*?\\)\\( \\|\t\\|$\\)") (defconst ledger-account-cleared-regex "\\(^[ \t]+\\)\\(*\\s-*.*?\\)\\( \\|\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)))) (addend 0) last-group) (if (null args) (progn (nconc defs (list `(defconst ,(intern (concat "ledger-regex-" (symbol-name name) "-group")) 1))) (nconc defs (list `(defconst ,(intern (concat "ledger-regex-" (symbol-name name) "-group--count")) 1))) (nconc defs (list `(defmacro ,(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)))) (setq last-group (or grouping target)))) (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 iso-date ( let ((sep '(or ?- ?/))) (rx (group (and (? (and (group (= 4 num))) (eval sep)) (group (and num (? num))) (eval sep) (group (and num (? num))))))) "Match a single date, in its 'written' form.") (ledger-define-regexp full-date (macroexpand `(rx (and (regexp ,ledger-iso-date-regexp) (? (and ?= (regexp ,ledger-iso-date-regexp)))))) "Match a compound date, of the form ACTUAL=EFFECTIVE" (actual iso-date) (effective iso-date)) (ledger-define-regexp state (rx (group (any ?! ?*))) "Match a transaction or posting's \"state\" character.") (ledger-define-regexp code (rx (and ?\( (group (+? (not (any ?\))))) ?\))) "Match the transaction code.") (ledger-define-regexp long-space (rx (and (*? blank) (or (and ? (or ? ?\t)) ?\t))) "Match a \"long space\".") (ledger-define-regexp note (rx (group (+ nonl))) "") (ledger-define-regexp end-note (macroexpand `(rx (and (regexp ,ledger-long-space-regexp) ?\; (regexp ,ledger-note-regexp)))) "") (ledger-define-regexp full-note (macroexpand `(rx (and line-start (+ blank) ?\; (regexp ,ledger-note-regexp)))) "") (ledger-define-regexp xact-line (macroexpand `(rx (and line-start (regexp ,ledger-full-date-regexp) (? (and (+ blank) (regexp ,ledger-state-regexp))) (? (and (+ blank) (regexp ,ledger-code-regexp))) (+ blank) (+? nonl) (? (regexp ,ledger-end-note-regexp)) line-end))) "Match a transaction's first line (and optional notes)." (actual-date full-date actual) (effective-date full-date effective) state code (note end-note)) (ledger-define-regexp recurring-line (macroexpand `(rx (and line-start (regexp "\\[.+/.+/.+\\]") (? (and (+ blank) (regexp ,ledger-state-regexp))) (? (and (+ blank) (regexp ,ledger-code-regexp))) (+ blank) (+? nonl) (? (regexp ,ledger-end-note-regexp)) line-end))) "Match a transaction's first line (and optional notes)." (actual-date full-date actual) (effective-date full-date effective) state code (note end-note)) (ledger-define-regexp account (rx (group (and (not (any blank ?\[ ?\( ?: ?\;)) (*? nonl)))) "") (ledger-define-regexp account-kind (rx (group (? (any ?\[ ?\()))) "") (ledger-define-regexp full-account (macroexpand `(rx (and (regexp ,ledger-account-kind-regexp) (regexp ,ledger-account-regexp) (? (any ?\] ?\)))))) "" (kind account-kind) (name account)) (ledger-define-regexp commodity (rx (group (or (and ?\" (+ (not (any ?\"))) ?\") (not (any blank ?\n digit ?- ?\[ ?\] ?. ?, ?\; ?+ ?* ?/ ?^ ?? ?: ?& ?| ?! ?= ?\< ?\> ?\{ ?\} ?\( ?\) ?@))))) "") (ledger-define-regexp amount (rx (group (and (? ?-) (and (+ digit) (*? (and (any ?. ?,) (+ digit)))) (? (and (any ?. ?,) (+ digit)))))) "") (ledger-define-regexp commoditized-amount (macroexpand `(rx (group (or (and (regexp ,ledger-commodity-regexp) (*? blank) (regexp ,ledger-amount-regexp)) (and (regexp ,ledger-amount-regexp) (*? blank) (regexp ,ledger-commodity-regexp)))))) "") (ledger-define-regexp commodity-annotations (macroexpand `(rx (* (+ blank) (or (and ?\{ (regexp ,ledger-commoditized-amount-regexp) ?\}) (and ?\[ (regexp ,ledger-iso-date-regexp) ?\]) (and ?\( (not (any ?\))) ?\)))))) "") (ledger-define-regexp cost (macroexpand `(rx (and (or "@" "@@") (+ blank) (regexp ,ledger-commoditized-amount-regexp)))) "") (ledger-define-regexp balance-assertion (macroexpand `(rx (and ?= (+ blank) (regexp ,ledger-commoditized-amount-regexp)))) "") (ledger-define-regexp full-amount (macroexpand `(rx (group (+? (not (any ?\;)))))) "") (ledger-define-regexp post-line (macroexpand `(rx (and line-start (+ blank) (? (and (regexp ,ledger-state-regexp) (* blank))) (regexp ,ledger-full-account-regexp) (? (and (regexp ,ledger-long-space-regexp) (regexp ,ledger-full-amount-regexp))) (? (regexp ,ledger-end-note-regexp)) line-end))) "" state (account-kind full-account kind) (account full-account name) (amount full-amount) (note end-note)) (defconst ledger-iterate-regex (concat "\\(\\(?:Y\\|year\\)\\s-+\\([0-9]+\\)\\|" ;; Catches a Y/year directive ledger-iso-date-regexp "\\([ *!]+\\)" ;; mark "\\((.*)\\)?" ;; code "\\([[:word:] ]+\\)" ;; desc "\\)")) (defconst ledger-xact-start-regex (concat "^" ledger-iso-date-regexp ;; subexp 1 "\\(=" ledger-iso-date-regexp "\\)?" )) (defconst ledger-xact-after-date-regex (concat "\\([ \t]+[*!]\\)?" ;; mark, subexp 1 "\\([ \t]+(.*?)\\)?" ;; code, subexp 2 "\\([ \t]+[^;\n]+\\)" ;; desc, subexp 3 "\\(;[^\n]*\\)?" ;; comment, subexp 4 )) (defconst ledger-posting-regex (concat "^[ \t]+ ?" ;; initial white space "\\([*!]\\)? ?" ;; state, subexpr 1 "\\([[:print:]]+\\([ \t][ \t]\\)\\)" ;; account, subexpr 2 "\\([^;\n]*\\)" ;; amount, subexpr 4 "\\(.*\\)" ;; comment, subexpr 5 )) (defconst ledger-directive-start-regex "[=~;#%|\\*[A-Za-z]") (provide 'ledger-regex) �������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-report.el������������������������������������������������������������0000664�0000000�0000000�00000044255�12660234410�0020032�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-report.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Provide facilities for running and saving reports in emacs ;;; Code: (require 'easymenu) (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)") ("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-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) ("tagname" . ledger-report-tagname-format-specifier) ("tagvalue" . ledger-report-tagvalue-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-report) (defcustom ledger-report-auto-refresh t "If t then automatically rerun the report when the ledger buffer is saved." :type 'boolean :group 'ledger-report) (defcustom ledger-report-auto-refresh-sticky-cursor nil "If t then try to place cursor at same relative position as it was before auto-refresh." :type 'boolean :group 'ledger-report) (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-saved nil) (defvar ledger-minibuffer-history nil) (defvar ledger-report-mode-abbrev-table) (defvar ledger-report-is-reversed nil) (defvar ledger-report-cursor-line-number nil) (defun ledger-report-reverse-report () "Reverse the order of the report." (interactive) (ledger-report-reverse-lines) (setq ledger-report-is-reversed (not ledger-report-is-reversed))) (defun ledger-report-reverse-lines () (goto-char (point-min)) (forward-paragraph) (forward-line) (save-excursion (setq inhibit-read-only t) (reverse-region (point) (point-max)))) (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 [(shift ?r)] 'ledger-report-reverse-report) (define-key map [?s] 'ledger-report-save) (define-key map [?k] 'ledger-report-kill) (define-key map [?e] 'ledger-report-edit-report) (define-key map [( shift ?e)] 'ledger-report-edit-reports) (define-key map [?q] 'ledger-report-quit) (define-key map [?g] 'ledger-report-redo) (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) map) "Keymap for `ledger-report-mode'.") (easy-menu-define ledger-report-mode-menu ledger-report-mode-map "Ledger report menu" '("Reports" ["Save Report" ledger-report-save] ["Edit Current Report" ledger-report-edit-report] ["Edit All Reports" ledger-report-edit-reports] ["Re-run Report" ledger-report-redo] "---" ["Reverse report order" ledger-report-reverse-report] "---" ["Scroll Up" scroll-up] ["Visit Source" ledger-report-visit-source] ["Scroll Down" scroll-down] "---" ["Quit" ledger-report-quit] )) (define-derived-mode ledger-report-mode text-mode "Ledger-Report" "A mode for viewing ledger reports.") (defun ledger-report-tagname-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 "Tag Name: " nil)) (defun ledger-report-tagvalue-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 "Tag Value: " nil)) (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 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 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-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) (set (make-local-variable 'ledger-report-is-reversed) nil) (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 ledger-report-string-empty-p (s) "Check S 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 (ledger-report-string-empty-p name) (car (assoc name ledger-reports)))) (defun ledger-reports-add (name cmd) "Add a new report NAME and CMD 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 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. 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 (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))) (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 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-xact-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 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. (ledger-read-account-with-prompt "Account")) (defun ledger-report-expand-format-specifiers (report-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 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 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 (ledger-report-string-empty-p report-name) (ledger-report-name-exists report-name) (progn (ledger-reports-add report-name report-cmd) (ledger-reports-custom-save))) report-cmd)) (defun ledger-do-report (cmd) "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\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-navigate-to-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." (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) (let ((cur-buf (current-buffer))) (if (and ledger-report-auto-refresh (or (string= (format-mode-line 'mode-name) "Ledger") (string= (format-mode-line 'mode-name) "Ledger-Report")) (get-buffer ledger-report-buffer-name)) (progn (pop-to-buffer (get-buffer ledger-report-buffer-name)) (shrink-window-if-larger-than-buffer) (setq buffer-read-only nil) (setq ledger-report-cursor-line-number (line-number-at-pos)) (erase-buffer) (ledger-do-report ledger-report-cmd) (setq buffer-read-only nil) (if ledger-report-is-reversed (ledger-report-reverse-lines)) (if ledger-report-auto-refresh-sticky-cursor (forward-line (- ledger-report-cursor-line-number 5))) (pop-to-buffer cur-buf))))) (defun ledger-report-quit () "Quit the ledger report buffer." (interactive) (ledger-report-goto) (set-window-configuration ledger-original-window-cfg) (kill-buffer (get-buffer ledger-report-buffer-name))) (defun ledger-report-edit-reports () "Edit the defined ledger reports." (interactive) (customize-variable 'ledger-reports)) (defun ledger-report-edit-report () (interactive) "Edit the current report command in the mini buffer and re-run the report" (setq ledger-report-cmd (ledger-report-read-command ledger-report-cmd)) (ledger-report-redo)) (defun ledger-report-read-new-name () "Read the name for a new report from the minibuffer." (let ((name "")) (while (ledger-report-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 (ledger-report-string-empty-p ledger-report-name) (setq ledger-report-name (ledger-report-read-new-name))) (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 'ledger-report) ;;; ledger-report.el ends here ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-schedule.el����������������������������������������������������������0000664�0000000�0000000�00000034563�12660234410�0020314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 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. (require 'ledger-init) (require 'cl) ;;; Code: (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 "~/ledger-schedule.ledger" "File to find scheduled transactions." :type 'file :group 'ledger-schedule) (defcustom ledger-schedule-week-days '(("Mo" 1) ("Tu" 2) ("We" 3) ("Th" 4) ("Fr" 5) ("Sa" 6) ("Su" 7)) "List of weekday abbreviations. There must be exactly seven entries each with a two character abbreviation for a day and the number of that day in the week. " :type '(alist :value-type (group integer)) :group 'ledger-schedule) (defsubst between (val low high) "Return TRUE if VAL > LOW and < 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))) (defun ledger-schedule-encode-day-of-week (day-string) "Return the numerical day of week corresponding to DAY-STRING." (cadr (assoc day-string ledger-schedule-week-days))) ;; Macros to handle date expressions (defun ledger-schedule-constrain-day-in-month (count day-of-week) "Return a form that returns TRUE for the 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-OF-WEEK skipping SKIP, starting on START-DATE. For example every second Friday, regardless of month." (let ((start-day (nth 6 (decode-time start-date)))) (if (eq start-day day-of-week) ;; good, can proceed `(zerop (mod (- (time-to-days date) ,(time-to-days 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-scan-transactions (schedule-file) "Scan SCHEDULE-FILE and return a list of transactions with date predicates. The car of each item is a function 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-descriptor (ledger-schedule-read-descriptor-tree (buffer-substring-no-properties (match-beginning 0) (match-end 0)))) (forward-paragraph) (setq transaction (list date-descriptor (buffer-substring-no-properties xact-start (point)))) (setq xact-list (cons transaction xact-list)))) xact-list))) (defun ledger-schedule-read-descriptor-tree (descriptor-string) "Read DESCRIPTOR-STRING and return a form that evaluates dates." (ledger-schedule-transform-auto-tree (split-string (substring descriptor-string 1 (string-match "]" descriptor-string)) " "))) (defun ledger-schedule-transform-auto-tree (descriptor-string-list) "Take DESCRIPTOR-STRING-LIST, and return 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 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))) (if (string-match "[A-Za-z]" descriptor-string) (ledger-schedule-constrain-day (nth 0 fields) (nth 1 fields) (nth 2 fields)) (list 'and (ledger-schedule-constrain-day (nth 0 fields) (nth 1 fields) (nth 2 fields)) (ledger-schedule-constrain-year (nth 0 fields) (nth 1 fields) (nth 2 fields)) (ledger-schedule-constrain-month (nth 0 fields) (nth 1 fields) (nth 2 fields)))))) (defun ledger-schedule-constrain-year (year-desc month-desc day-desc) "Return a form that constrains the year. YEAR-DESC, MONT-DESC, and DAY-DESC are the string portions of the date descriptor." (cond ((string= year-desc "*") t) ((/= 0 (string-to-number year-desc)) `(memq (nth 5 (decode-time date)) ',(mapcar 'string-to-number (split-string year-desc ",")))) (t (error "Improperly specified year constraint: %s %s %s" year-desc month-desc day-desc)))) (defun ledger-schedule-constrain-month (year-desc month-desc day-desc) "Return a form that constrains the month. YEAR-DESC, MONT-DESC, and DAY-DESC are the string portions of the date descriptor." (cond ((string= month-desc "*") t) ;; always match ((string= month-desc "E") ;; Even `(evenp (nth 4 (decode-time date)))) ((string= month-desc "O") ;; Odd `(oddp (nth 4 (decode-time date)))) ((/= 0 (string-to-number month-desc)) ;; Starts with number `(memq (nth 4 (decode-time date)) ',(mapcar 'string-to-number (split-string month-desc ",")))) (t (error "Improperly specified month constraint: %s %s %s" year-desc month-desc day-desc)))) (defun ledger-schedule-constrain-day (year-desc month-desc day-desc) "Return a form that constrains the day. YEAR-DESC, MONT-DESC, and DAY-DESC are the string portions of the date descriptor." (cond ((string= day-desc "*") t) ((string-match "[A-Za-z]" day-desc) ;; There is something other than digits and commas (ledger-schedule-parse-complex-date year-desc month-desc day-desc)) ((/= 0 (string-to-number day-desc)) `(memq (nth 3 (decode-time date)) ',(mapcar 'string-to-number (split-string day-desc ",")))) (t (error "Improperly specified day constraint: %s %s %s" year-desc month-desc day-desc)))) (defun ledger-schedule-parse-complex-date (year-desc month-desc day-desc) "Parse day descriptors that have repeats." (let ((years (mapcar 'string-to-number (split-string year-desc ","))) (months (mapcar 'string-to-number (split-string month-desc ","))) (day-parts (split-string day-desc "+")) (every-nth (string-match "+" day-desc))) (if every-nth (let ((base-day (string-to-number (car day-parts))) (increment (string-to-number (substring (cadr day-parts) 0 (string-match "[A-Za-z]" (cadr day-parts))))) (day-of-week (ledger-schedule-encode-day-of-week (substring (cadr day-parts) (string-match "[A-Za-z]" (cadr day-parts)))))) (ledger-schedule-constrain-every-count-day day-of-week increment (encode-time 0 0 0 base-day (car months) (car years)))) (let ((count (string-to-number (substring (car day-parts) 0 1))) (day-of-week (ledger-schedule-encode-day-of-week (substring (car day-parts) (string-match "[A-Za-z]" (car day-parts)))))) (ledger-schedule-constrain-day-in-month count day-of-week))))) (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-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 (or (cdr (assoc "date-format" ledger-environment-alist)) ledger-default-date-format))) (with-current-buffer schedule-buf (erase-buffer) (dolist (candidate candidates) (insert (format-time-string date-format (car candidate) ) " " (cadr candidate) "\n")) (ledger-mode)) (length candidates))) (defun ledger-schedule-upcoming (file look-backward look-forward) "Generate upcoming transactions. FILE is the file containing the scheduled transaction, default to `ledger-schedule-file'. LOOK-BACKWARD is the number of day in the past to look at default to `ledger-schedule-look-backward' LOOK-FORWARD is the number of day in the futur to look at default to `ledger-schedule-look-forward' Use a prefix arg to change the default value" (interactive (if current-prefix-arg (list (read-file-name "Schedule File: " () ledger-schedule-file t) (read-number "Look backward: " ledger-schedule-look-backward) (read-number "Look forward: " ledger-schedule-look-forward)) (list ledger-schedule-file ledger-schedule-look-backward ledger-schedule-look-forward))) (if (and file (file-exists-p file)) (progn (ledger-schedule-create-auto-buffer (ledger-schedule-scan-transactions file) look-backward look-forward (current-buffer)) (pop-to-buffer ledger-schedule-buffer-name)) (error "Could not find ledger schedule file at %s" file))) (provide 'ledger-schedule) ;;; ledger-schedule.el ends here ���������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-sort.el��������������������������������������������������������������0000664�0000000�0000000�00000007565�12660234410�0017511�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-xact.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; ;;; Code: (defun ledger-sort-find-start () "Find the beginning of a sort region" (if (re-search-forward ";.*Ledger-mode:.*Start sort" nil t) (match-end 0))) (defun ledger-sort-find-end () "Find the end of a sort region" (if (re-search-forward ";.*Ledger-mode:.*End sort" nil t) (match-end 0))) (defun ledger-sort-insert-start-mark () "Insert a marker to start a sort region" (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 () "Insert a marker to end a sort region" (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) point-delta (bounds (ledger-navigate-find-xact-extents (point))) target-xact) (setq point-delta (- (point) (car bounds))) (setq target-xact (buffer-substring (car bounds) (cadr bounds))) (setq inhibit-modification-hooks t) (save-excursion (save-restriction (goto-char beg) ;; make sure point is at the beginning of a xact (ledger-navigate-next-xact) (unless (looking-at ledger-payee-any-status-regex) (ledger-navigate-next-xact)) (setq new-beg (point)) (goto-char end) (ledger-navigate-next-xact) ;; 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-navigate-next-xact 'ledger-navigate-end-of-xact 'ledger-sort-startkey)))) (goto-char (point-min)) (re-search-forward (regexp-quote target-xact)) (goto-char (+ (match-beginning 0) point-delta)) (setq inhibit-modification-hooks nil))) (defun ledger-sort-buffer () "Sort the entire buffer." (interactive) (let (sort-start sort-end) (save-excursion (goto-char (point-min)) (setq 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 'ledger-sort) ;;; ledger-sort.el ends here �������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-state.el�������������������������������������������������������������0000664�0000000�0000000�00000022706�12660234410�0017634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-state.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 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-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-syntax-forward " ") (cond ((looking-at "!\\s-*") 'pending) ((looking-at "\\*\\s-*") 'cleared) (t nil))))) (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-transaction-state))))) (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-state-from-string (state-string) "Get state from STATE-CHAR." (when state-string (cond ((string-match "\\!" state-string) 'pending) ((string-match "\\*" state-string) 'cleared) ((string-match ";" state-string) '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'). 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 xact, as well as ensuring that the most minimal display format is used. This could be 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-navigate-find-xact-extents (point))) new-status cur-status) ;; Uncompact the xact, to make it easier to toggle the ;; transaction (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./=\\-") ;; skip the date (skip-chars-forward " \t") ;; skip the white space after 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))) (when (> width 0) (delete-region here (point)) (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") (when (not (eq (ledger-state-from-char (char-after)) 'comment)) (insert (ledger-char-from-state cur-status) " ") (if (and (search-forward " " (line-end-position) t) (looking-at " ")) (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)) (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 nil) (hetero nil)) (while (and (not hetero) (looking-at "[ \t]")) (skip-chars-forward " \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) (not (eq state nil))) (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./=\\-") ;; Skip the date (skip-chars-forward " \t") ;; Skip the white space (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))))))) new-status)) (defun ledger-toggle-current (&optional style) "Toggle the current thing at point with optional STYLE." (interactive) (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 'transaction (ledger-thing-at-point))))) (if (looking-at "\\s-+[*!]") (ledger-toggle-current-posting style)) (forward-line) (goto-char (line-beginning-position)))) (ledger-toggle-current-transaction style)) (ledger-toggle-current-posting style))) (defun ledger-toggle-current-transaction (&optional style) "Toggle the transaction at point using optional STYLE." (interactive) (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 'ledger-state) ;;; ledger-state.el ends here ����������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-test.el��������������������������������������������������������������0000664�0000000�0000000�00000011664�12660234410�0017474�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-test.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;;; Code: (declare-function ledger-mode "ledger-mode") ; TODO: fix this cyclic dependency (declare-function org-narrow-to-subtree "org") (declare-function org-entry-get "org") (declare-function outline-back-to-heading "outline") (declare-function outline-next-heading "outline") (defgroup ledger-test nil "Definitions for the Ledger testing framework" :group '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-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) (narrow-to-region (point) (progn (outline-next-heading) (point))) (goto-char (point-min))) (defun ledger-test-create () (interactive) (let ((uuid (org-entry-get (point) "ID"))) (when (string-match "\\`\\([^-]+\\)-" uuid) (let ((prefix (match-string 1 uuid)) input output) (save-restriction (ledger-test-org-narrow-to-entry) (goto-char (point-min)) (while (re-search-forward "#\\+begin_src ledger" nil t) (goto-char (match-end 0)) (forward-line 1) (let ((beg (point))) (re-search-forward "#\\+end_src") (setq input (concat (or input "") (buffer-substring beg (match-beginning 0)))))) (goto-char (point-min)) (while (re-search-forward ":OUTPUT:" nil t) (goto-char (match-end 0)) (forward-line 1) (let ((beg (point))) (re-search-forward ":END:") (setq output (concat (or output "") (buffer-substring beg (match-beginning 0))))))) (find-file-other-window (expand-file-name (concat prefix ".test") (expand-file-name "test/regress" ledger-source-directory))) (ledger-mode) (if input (insert input) (insert "2012-03-17 Payee\n") (insert " Expenses:Food $20\n") (insert " Assets:Cash\n")) (insert "\ntest reg\n") (if output (insert output)) (insert "end test\n"))))) (defun ledger-test-run () (interactive) (save-excursion (goto-char (point-min)) (when (re-search-forward "^test \\(.+?\\)\\( ->.*\\)?$" nil t) (let ((command (expand-file-name ledger-test-binary)) (args (format "--args-only --columns=80 --no-color -f \"%s\" %s" buffer-file-name (match-string 1)))) (setq args (replace-regexp-in-string "\\$sourcepath" ledger-source-directory args)) (kill-new args) (message "Testing: ledger %s" args) (let ((prev-directory default-directory)) (cd ledger-source-directory) (unwind-protect (async-shell-command (format "\"%s\" %s" command args)) (cd prev-directory))))))) (provide 'ledger-test) ;;; ledger-test.el ends here ����������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-texi.el��������������������������������������������������������������0000664�0000000�0000000�00000015225�12660234410�0017463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-texi.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 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) (goto-char (point-min)) (let ((command (buffer-substring (point-min) (line-end-position))) input) (re-search-forward "^<<<\n") (let ((beg (point)) end) (re-search-forward "^>>>") (setq end (match-beginning 0)) (forward-line 1) (let ((output-beg (point))) (re-search-forward "^>>>") (goto-char (match-beginning 0)) (delete-region output-beg (point)) (apply #'call-process-region beg end (expand-file-name "~/Products/ledger/debug/ledger") nil t nil "-f" "-" "--args-only" "--columns=80" "--no-color" (split-string command " ")))))) (defun ledger-texi-write-test (name command input output &optional category) (let ((buf (current-buffer))) (with-current-buffer (find-file-noselect (expand-file-name (concat name ".test") category)) (erase-buffer) (let ((case-fold-search nil)) (if (string-match "\\$LEDGER\\s-+" command) (setq command (replace-match "" t t command))) (if (string-match " -f \\$\\([-a-z]+\\)" command) (setq command (replace-match "" t t command)))) (insert command ?\n) (insert "<<<" ?\n) (insert input) (insert ">>>1" ?\n) (insert output) (insert ">>>2" ?\n) (insert "=== 0" ?\n) (save-buffer) (unless (eq buf (current-buffer)) (kill-buffer (current-buffer)))))) (defun ledger-texi-update-test () (interactive) (let ((details (ledger-texi-test-details)) (name (file-name-sans-extension (file-name-nondirectory (buffer-file-name))))) (ledger-texi-write-test name (nth 0 details) (nth 1 details) (ledger-texi-invoke-command (ledger-texi-expand-command (nth 0 details) (ledger-texi-write-test-data name (nth 1 details))))))) (defun ledger-texi-test-details () (goto-char (point-min)) (let ((command (buffer-substring (point) (line-end-position))) input output) (re-search-forward "^<<<") (let ((input-beg (1+ (match-end 0)))) (re-search-forward "^>>>1") (let ((output-beg (1+ (match-end 0)))) (setq input (buffer-substring input-beg (match-beginning 0))) (re-search-forward "^>>>2") (setq output (buffer-substring output-beg (match-beginning 0))) (list command input output))))) (defun ledger-texi-expand-command (command data-file) (if (string-match "\\$LEDGER" 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)))) (defun ledger-texi-write-test-data (name input) (let ((path (expand-file-name name temporary-file-directory))) (with-current-buffer (find-file-noselect path) (erase-buffer) (insert input) (save-buffer)) path)) (defun ledger-texi-update-examples () (interactive) (save-excursion (goto-char (point-min)) (while (re-search-forward "^@c \\(\\(?:sm\\)?ex\\) \\(\\S-+\\): \\(.*\\)" nil t) (let ((section (match-string 1)) (example-name (match-string 2)) (command (match-string 3)) expanded-command (data-file ledger-texi-sample-doc-path) input output) (goto-char (match-end 0)) (forward-line) (when (looking-at "@\\(\\(?:small\\)?example\\)") (let ((beg (point))) (re-search-forward "^@end \\(\\(?:small\\)?example\\)") (delete-region beg (1+ (point))))) (when (let ((case-fold-search nil)) (string-match " -f \\$\\([-a-z]+\\)" command)) (let ((label (match-string 1 command))) (setq command (replace-match "" t t command)) (save-excursion (goto-char (point-min)) (search-forward (format "@c data: %s" label)) (re-search-forward "@\\(\\(?:small\\)?example\\)") (forward-line) (let ((beg (point))) (re-search-forward "@end \\(\\(?:small\\)?example\\)") (setq data-file (ledger-texi-write-test-data (format "%s.dat" label) (buffer-substring-no-properties beg (match-beginning 0)))))))) (let ((section-name (if (string= section "smex") "smallexample" "example")) (output (ledger-texi-invoke-command (ledger-texi-expand-command command data-file)))) (insert "@" section-name ?\n output "@end " section-name ?\n)) ;; Update the regression test associated with this example (ledger-texi-write-test example-name command input output "../test/manual"))))) (provide 'ledger-texi) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/lisp/ledger-xact.el��������������������������������������������������������������0000664�0000000�0000000�00000016776�12660234410�0017465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������;;; ledger-xact.el --- Helper code for use with the "ledger" command-line tool ;; Copyright (C) 2003-2016 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., 51 Franklin Street, Fifth Floor, Boston, ;; MA 02110-1301 USA. ;;; Commentary: ;; Utilities for running ledger synchronously. ;;; Code: (require 'eshell) (require 'ledger-regex) (require 'ledger-navigate) ;; TODO: This file depends on code in ledger-mode.el, which depends on this. (defcustom ledger-highlight-xact-under-point t "If t highlight xact under point." :type 'boolean :group 'ledger) (defcustom ledger-use-iso-dates nil "If non-nil, use the iso-8601 format for dates (YYYY-MM-DD)." :type 'boolean :group 'ledger :safe t) (defvar ledger-xact-highlight-overlay (list)) (make-variable-buffer-local 'ledger-xact-highlight-overlay) (defun ledger-highlight-xact-under-point () "Move the highlight overlay to the current transaction." (if ledger-highlight-xact-under-point (let ((exts (ledger-navigate-find-element-extents (point))) (ovl ledger-xact-highlight-overlay)) (if (not ledger-xact-highlight-overlay) (setq ovl (setq ledger-xact-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 '(nil . 99))))) (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" (let (last-xact-start) (catch 'found (ledger-xact-iterate-transactions (function (lambda (start date mark desc) (setq last-xact-start start) (if (ledger-time-less-p moment date) (throw 'found t)))))) (when (and (eobp) last-xact-start) (let ((end (cadr (ledger-navigate-find-xact-extents last-xact-start)))) (goto-char end) (insert "\n") (forward-line))))) (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)))) (defun ledger-year-and-month () (let ((sep (if ledger-use-iso-dates "-" "/"))) (concat ledger-year sep ledger-month sep))) (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 (ledger-read-date "Copy to date: "))) (let* ((here (point)) (extents (ledger-navigate-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") (beginning-of-line -1) (ledger-navigate-beginning-of-xact) (re-search-forward ledger-iso-date-regexp) (replace-match date) (ledger-next-amount) (if (re-search-forward "[-0-9]") (goto-char (match-beginning 0))))) (defun ledger-delete-current-transaction (pos) "Delete the transaction surrounging POS." (interactive "d") (let ((bounds (ledger-navigate-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 ;; Note: This isn't "just" the date - it can contain ;; other text too (ledger-read-date "Transaction: "))) (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))) (ledger-post-align-postings (point-min) (point-max)) (buffer-string))) "\n")) (progn (insert (car args) " \n\n") (end-of-line -1))))) (provide 'ledger-xact) ;;; ledger-xact.el ends here ��ledger-3.1.1+dfsg1/python/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0015275�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/__init__.py���������������������������������������������������������������0000664�0000000�0000000�00000000000�12660234410�0017374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/demo.py�������������������������������������������������������������������0000775�0000000�0000000�00000024551�12660234410�0016605�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python import sys from datetime import datetime # The following literate program will demonstrate, by example, how to use the # Ledger Python module to access your data and build custom reports using the # magic of Python. import ledger print "Welcome to the Ledger.Python demo!" # Some quick helper functions to help us assert various types of truth # throughout the script. def assertEqual(pat, candidate): if pat != candidate: raise Exception("FAILED: %s != %s" % (pat, candidate)) sys.exit(1) ############################################################################### # # COMMODITIES # # Every amount in Ledger has a commodity, even if it is the "null commodity". # What's special about commodities are not just their symbol, but how they # alter the way amounts are displayed. # # For example, internally Ledger uses infinite precision rational numbers, # which have no decimal point. So how does it know that $1.00 / $0.75 should # be displayed as $1.33, and not with an infinitely repeating decimal? It # does it by consulting the commodity. # # Whenever an amount is encountered in your data file, Ledger observes how you # specified it: # - How many digits of precision did you use? # - Was the commodity name before or after the amount? # - Was the commodity separated from the amount by a space? # - Did you use thousands markers (1,000)? # - Did you use European-style numbers (1.000,00)? # # By tracking this information for each commodity, Ledger knows how you want # to see the amount in your reports. This way, dollars can be output as # $123.56, while stock options could be output as 10.113 AAPL. # # Your program can access the known set of commodities using the global # `ledger.commodities'. This object behaves like a dict, and support all of # the non-modifying dict protocol methods. If you wish to create a new # commodity without parsing an amount, you can use the method # `find_or_create': comms = ledger.commodities usd = comms.find_or_create('$') eur = comms.find_or_create('EUR') xcd = comms.find_or_create('XCD') assert not comms.find('CAD') assert not comms.has_key('CAD') assert not 'CAD' in comms # The above mentioned commodity display attributes can be set using commodity # display flags. This is not something you will usually be doing, however, as # these flags can be inferred correctly from a large enough set of sample # amounts, such as those found in your data file. If you live in Europe and # want all amounts to default to the European-style, set the static variable # `european_by_default'. eur.add_flags(ledger.COMMODITY_STYLE_DECIMAL_COMMA) assert eur.has_flags(ledger.COMMODITY_STYLE_DECIMAL_COMMA) assert not eur.has_flags(ledger.COMMODITY_STYLE_THOUSANDS) comms.european_by_default = True # There are a few built-in commodities: null, %, h, m and s. Normally you # don't need to worry about them, but they'll show up if you examine all the # keys in the commodities dict. assertEqual([u'', u'$', u'%', u'EUR', u'XCD', u'h', u'm', u's'], sorted(comms.keys())) # All the styles of dict iteration are supported: for symbol in comms.iterkeys(): pass for commodity in comms.itervalues(): pass #for symbol, commodity in comms.iteritems(): # pass #for symbol, commodity in comms: # pass # Another important thing about commodities is that they remember if they've # been exchanged for another commodity, and what the conversion rate was on # that date. You can record specific conversion rates for any date using the # `exchange' method. comms.exchange(eur, ledger.Amount('$0.77')) # Trade 1 EUR for $0.77 comms.exchange(eur, ledger.Amount('$0.66'), datetime.now()) # For the most part, however, you won't be interacting with commodities # directly, except maybe to look at their `symbol'. assertEqual('$', usd.symbol) assertEqual('$', comms['$'].symbol) ############################################################################### # # AMOUNTS & BALANCES # # Ledger deals with two basic numerical values: Amount and Balance objects. # An Amount is an infinite-precision rational with an associated commodity # (even if it is the null commodity, which is called an "uncommoditized # amount"). A Balance is a collection of Amounts of differing commodities. # # Amounts support all the math operations you might expect of an integer, # except it carries a commodity. Let's take dollars for example: zero = ledger.Amount("$0") one = ledger.Amount("$1") oneb = ledger.Amount("$1") two = ledger.Amount("$2") three = ledger.Amount("3") # uncommoditized assert one == oneb # numeric equality, not identity assert one != two assert not zero # tests if it would *display* as a zero assert one < two assert one > zero # For addition and subtraction, only amounts of the same commodity may be # used, unless one of the amounts has no commodity at all -- in which case the # result uses the commodity of the other value. Adding $10 to 10 EUR, for # example, causes an ArithmeticError exception, but adding 10 to $10 gives # $20. four = ledger.Amount(two) # make a copy four += two assertEqual(four, two + two) assertEqual(zero, one - one) try: two += ledger.Amount("20 EUR") assert False except ArithmeticError: pass # Use `number' to get the uncommoditized version of an Amount assertEqual(three, (two + one).number()) # Multiplication and division does supports Amounts of different commodities, # however: # - If either amount is uncommoditized, the result carries the commodity of # the other amount. # - Otherwise, the result always carries the commodity of the first amount. five = ledger.Amount("5 CAD") assertEqual(one, two / two) assertEqual(five, (five * ledger.Amount("$2")) - ledger.Amount("5")) # An amount's commodity determines the decimal precision it's displayed with. # However, this "precision" is a notional thing only. You can tell an amount # to ignore its display precision by setting `keep_precision' to True. # (Uncommoditized amounts ignore the value of `keep_precision', and assume it # is always True). In this case, Ledger does its best to maintain maximal # precision by watching how the Amount is used. That is, 1.01 * 1.01 yields a # precision of 4. This tracking is just a best estimate, however, since # internally Ledger never uses floating-point values. amt = ledger.Amount('$100.12') mini = ledger.Amount('0.00045') assert not amt.keep_precision assertEqual(5, mini.precision) assertEqual(5, mini.display_precision) # display_precision == precision assertEqual(2, amt.precision) assertEqual(2, amt.display_precision) mini *= mini amt *= amt assertEqual(10, mini.precision) assertEqual(10, mini.display_precision) assertEqual(4, amt.precision) assertEqual(2, amt.display_precision) # There are several other supported math operations: amt = ledger.Amount('$100.12') market = ((ledger.Amount('1 EUR') / ledger.Amount('$0.77')) * amt) assertEqual(market, amt.value(eur)) # find present market value assertEqual('$-100.12', str(amt.negated())) # negate the amount assertEqual('$-100.12', str(- amt)) # negate it more simply assertEqual('$0.01', str(amt.inverted())) # reverse NUM/DEM assertEqual('$100.12', str(amt.rounded())) # round it to display precision assertEqual('$100.12', str(amt.truncated())) # truncate to display precision assertEqual('$100.00', str(amt.floored())) # floor it to nearest integral assertEqual('$100.12', str(abs(amt))) # absolute value assertEqual('$100.12', str(amt)) # render to a string assertEqual('100.12', amt.quantity_string()) # render quantity to a string assertEqual('100.12', str(amt.number())) # strip away commodity assertEqual(1, amt.sign()) # -1, 0 or 1 assert amt.is_nonzero() # True if display amount nonzero assert not amt.is_zero() # True if display amount is zero assert not amt.is_realzero() # True only if value is 0/0 assert not amt.is_null() # True if uninitialized # Amounts can also be converted the standard floats and integers, although # this is not recommend since it can lose precision. assertEqual(100.12, amt.to_double()) assert amt.fits_in_long() # there is no `fits_in_double' assertEqual(100, amt.to_long()) # Finally, amounts can be annotated to provide additional information about # "lots" of a given commodity. This example shows $100.12 that was purchased # on 2009/10/01 for 140 EUR. Lot information can be accessed through via the # Amount's `annotation' property. You can also strip away lot details to get # the underlying amount. If you want the total price of any Amount, by # multiplying by its per-unit lot price, call the `Amount.price' method # instead of the `Annotation.price' property. amt2 = ledger.Amount('$100.12 {140 EUR} [2009/10/01]') assert amt2.has_annotation() assertEqual(amt, amt2.strip_annotations()) assertEqual(ledger.Amount('140 EUR'), amt2.annotation.price) assertEqual(ledger.Amount('14016,8 EUR'), amt2.price()) # european amount! ############################################################################### # # VALUES # # As common as Amounts and Balances are, there is a more prevalent numeric # type you will encounter when generating reports: Value objects. A Value is # a variadic type that can be any of the following types: # - Amount # - Balance # - boolean # - integer # - datetime # - date # - string # - regex # - sequence # # The reason for the variadic type is that it supports dynamic self-promotion. # For example, it is illegal to add two Amounts of different commodities, but # it is not illegal to add two Value amounts of different commodities. In the # former case an exception in raised, but in the latter the Value simply # promotes itself to a Balance object to make the addition valid. # # Values are not used by any of Ledger's data objects (Journal, Transaction, # Posting or Account), but they are used extensively by value expressions. val = ledger.Value('$100.00') assert val.is_amount() assertEqual('$', val.to_amount().commodity.symbol) # JOURNALS #journal.find_account('') #journal.find_or_create_account('') # ACCOUNTS #account.name #account.fullname() #account.amount #account.total # TRANSACTIONS #txn.payee # POSTINGS #post.account # REPORTING #journal.collect('-M food') #journal.collect_accounts('^assets ^liab ^equity') print 'Demo completed successfully.' �������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0016066�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/asc.gif���������������������������������������������������������������0000664�0000000�0000000�00000000066�12660234410�0017325�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a����#-0!���,�������  ڛgk$-�;��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/bg.gif����������������������������������������������������������������0000664�0000000�0000000�00000000100�12660234410�0017134�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a� ���#-0!���,����� �� bxT2W>e`U��;����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/desc.gif��������������������������������������������������������������0000664�0000000�0000000�00000000066�12660234410�0017475�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a����#-0!���,������� ɭT2Y�;��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/icons/����������������������������������������������������������������0000775�0000000�0000000�00000000000�12660234410�0017201�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/icons/first.png�������������������������������������������������������0000664�0000000�0000000�00000001320�12660234410�0021032�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a���gAMA��7���tEXtSoftware�Adobe ImageReadyqe<��bIDAT8˥KoQ|Xt3bESWNqјBf(MkHMH[4:P@[Pp\&dqqݰNAQ@q5M' %ba}af```IJ:W=ZC|C_NS; z4sr\څQS%{~(,;�ec9_iAF8lGkCÞ2X6.8=l =r3@ z` |O3Z!`�&u@�RF. ,gыqTa$0M}+V|)* skup�ͨ[O}`N�҂2 +G�㗃[[PS:PĐT- �9yhsԂK1|z= ;t?# ½Dnvŀ QU2!^$`x|B RZvˋU=?EEO~YI`!bxNWps4~!jս)  ˊG`^g-᫬iyn[ai]\033{Lߏ/? -����IENDB`����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/icons/last.png��������������������������������������������������������0000664�0000000�0000000�00000001341�12660234410�0020651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a���gAMA��7���tEXtSoftware�Adobe ImageReadyqe<��sIDAT8˥KoQ|XtN|$_1ac⢩+'Ec�QL* ՠBjE:i h rs;oܜI]2BJZHѝIIʇu}ك! GƓbflU-`o՛BIP vw> j4;+V7Ǝ!cwӇ^YOہ`v!DI�fn ?:P !WmːMw( \'^x(f͜#<)6?^}ߎXKEӕ^IX!˷$T]7{PE/~`P[p&՝TZêeG/+=! c ! SnIUtai Pٍ5 G2+JE �px"L&W�H &dA$BM:L5 <DkY8ޒ7iN4'+-S|C߆('PJjg=RM$9lfi篇4,jd u29 ?'?ӽWiK̸\:3;37ؿ?|oz_5����IENDB`�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/icons/next.png��������������������������������������������������������0000664�0000000�0000000�00000001340�12660234410�0020663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a���gAMA��7���tEXtSoftware�Adobe ImageReadyqe<��rIDAT8˥SKoQ;` Vkҝ6&.1Bf,QL* ڦT P (Ps.&ޜ;; �ǵeШЩЫ@?x?NB{hp38ƝN@0,E7$3k0c<8MGPk!W(ȯ%( HFK͖xv*RVIr}~ɲE-@_X6f (hCG4׀\5;w7 hG:w`xJbn<b j \H)7IL$y oQ=cHUHY |"I 6ʰC. OB6\̈XS*@9a%lX7@`O$+N;}rX]{=R jB|2'' p2lhudAguƙt @$"9Q &쓩`c;| e0j枭dp>^I lj q5:qH� ~`jR2Y&VhIsίE_%Zo^=SXWY߫<J^2O8YxfbE3:[LߟO>_gt����IENDB`������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/icons/prev.png��������������������������������������������������������0000664�0000000�0000000�00000001351�12660234410�0020663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR���������a���gAMA��7���tEXtSoftware�Adobe ImageReadyqe<��{IDAT8˥MoQXtC#jQ&nLؘhZ#1Zcbڴ6PIJG( ( Ps.RuMLysΝs�`hPgf@&I!I)IՕ'3~xJ|MYmO}Qzgw C7LHP#֢c0k>mqf58+ *52"Afv59"&x6=~δA�/,3Ge uȕl<߇;9lG�Aaكf{<<Eq`)"@ڎ3:åNp�[&a%^;,V�?K |v.W{;V!ǰh^bK�@� &\Å�p2&th>@e+DTxҬ6$; Oz!vP#&.d0ŨT�6yc:@2OԠjh(k9R9= [20;ffkĈOL/0o m@cr* ,UER p0s4](k͂θ1HLPg�YfDQL_&/VyN)vBgGu6&e ̄ <:.����IENDB`���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/res/style.css�������������������������������������������������������������0000664�0000000�0000000�00000001620�12660234410�0017737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* tables */ table.tablesorter { font-family:arial; background-color: #CDCDCD; margin:10px 0pt 15px; font-size: 8pt; width: 100%; text-align: left; } table.tablesorter thead tr th, table.tablesorter tfoot tr th { background-color: #e6EEEE; border: 1px solid #FFF; font-size: 8pt; padding: 4px; } table.tablesorter thead tr .header { background-image: url(bg.gif); background-repeat: no-repeat; background-position: center right; cursor: pointer; } table.tablesorter tbody td { color: #3D3D3D; padding: 4px; background-color: #FFF; vertical-align: top; } table.tablesorter tbody tr.odd td { background-color:#F0F0F6; } table.tablesorter thead tr .headerSortUp { background-image: url(asc.gif); } table.tablesorter thead tr .headerSortDown { background-image: url(desc.gif); } table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp { background-color: #8dbdd8; } ����������������������������������������������������������������������������������������������������������������ledger-3.1.1+dfsg1/python/server.py�����������������������������������������������������������������0000664�0000000�0000000�00000014500�12660234410�0017155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import ledger import cgi import sys import types import posixpath import urllib import shutil import os import re from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from os.path import exists, join, isfile from Cheetah.Template import Template from Cheetah.Filters import Filter, WebSafe webroot = join(os.getcwd(), 'python', 'res') class UnicodeFilter(Filter): def filter(self, s, **kargs): return Filter.filter(self, s, str=unicode, **kargs) def strip(value): #return re.sub('\n', '<br />', value.strip_annotations().to_string()) return value.strip_annotations().to_string() templateDef = '''#encoding utf-8 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>$title

Register report

#for $post in $posts #set $total = $total + $post.amount #set $last_xact = $post.xact #end for
Date Payee Account Amount Total
Date Payee Account Amount Total
$post.xact.date $post.xact.payee $post.account ${strip($post.amount)} ${strip($total)}
''' class LedgerHandler(BaseHTTPRequestHandler): def __init__(self, *args): self.journal = ledger.Journal(sys.argv[1]) BaseHTTPRequestHandler.__init__(self, *args) def do_GET(self): path = self.translate_path(self.path) if path and exists(path) and isfile(path): self.copyfile(open(path), self.wfile) else: tmpl = Template(templateDef, filter=UnicodeFilter) tmpl.title = 'Ledger Journal' tmpl.posts = self.journal.collect(sys.argv[2]) tmpl.total = ledger.Value(0) tmpl.strip = strip tmpl.last_xact = None tmpl.empty = "" html = unicode(tmpl) html = html.encode('utf-8') self.wfile.write(html) def do_POST(self): print "Saw a POST request!" try: ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) if ctype == 'multipart/form-data': query = cgi.parse_multipart(self.rfile, pdict) self.send_response(301) self.end_headers() except Exception: print "Saw exception in POST handler" # This code is straight from SimpleHTTPServer.py def copyfile(self, source, outputfile): """Copy all data between two file objects. The SOURCE argument is a file object open for reading (or anything with a read() method) and the DESTINATION argument is a file object open for writing (or anything with a write() method). The only reason for overriding this would be to change the block size or perhaps to replace newlines by CRLF -- note however that this the default server uses this to copy binary data as well. """ shutil.copyfileobj(source, outputfile) def translate_path(self, path): """Translate a /-separated PATH to the local filename syntax. Components that mean special things to the local file system (e.g. drive or directory names) are ignored. (XXX They should probably be diagnosed.) """ # abandon query parameters path = path.split('?',1)[0] path = path.split('#',1)[0] path = posixpath.normpath(urllib.unquote(path)) words = path.split('/') words = filter(None, words) path = webroot for word in words: drive, word = os.path.splitdrive(word) head, word = os.path.split(word) if word in (os.curdir, os.pardir): continue path = os.path.join(path, word) return path def main(*args): try: port = 9000 server = HTTPServer(('', port), LedgerHandler) print "Local HTTP server listening on port %d... (Control-C to exit)" \ % port server.serve_forever() except KeyboardInterrupt: print "Shutting down server" server.socket.close() if __name__ == '__main__': if len(sys.argv) < 3: print "usage: server.py " sys.exit(1) main() ledger-3.1.1+dfsg1/src/000077500000000000000000000000001266023441000145435ustar00rootroot00000000000000ledger-3.1.1+dfsg1/src/CMakeLists.txt000066400000000000000000000175551266023441000173200ustar00rootroot00000000000000set(LEDGER_CLI_SOURCES global.cc main.cc) set(LEDGER_SOURCES stats.cc generate.cc csv.cc convert.cc draft.cc emacs.cc org.cc ptree.cc print.cc output.cc precmd.cc chain.cc filters.cc report.cc views.cc select.cc session.cc option.cc lookup.cc compare.cc iterators.cc timelog.cc textual.cc temps.cc journal.cc account.cc xact.cc post.cc item.cc format.cc query.cc scope.cc expr.cc op.cc parser.cc token.cc value.cc balance.cc quotes.cc history.cc pool.cc annotate.cc commodity.cc amount.cc stream.cc mask.cc times.cc error.cc utils.cc strptime.cc wcwidth.cc) if (HAVE_BOOST_PYTHON) list(APPEND LEDGER_SOURCES py_account.cc py_amount.cc py_balance.cc py_commodity.cc py_expr.cc py_format.cc py_item.cc py_journal.cc py_post.cc py_session.cc py_times.cc py_utils.cc py_value.cc py_xact.cc pyinterp.cc pyledger.cc) endif() set(LEDGER_INCLUDES account.h amount.h annotate.h balance.h chain.h commodity.h compare.h context.h convert.h csv.h draft.h emacs.h error.h expr.h exprbase.h filters.h flags.h format.h generate.h global.h history.h item.h iterators.h journal.h lookup.h mask.h op.h option.h org.h output.h parser.h pool.h post.h precmd.h predicate.h print.h pstream.h ptree.h pyfstream.h pyinterp.h pyutils.h query.h quotes.h report.h scope.h select.h session.h stats.h stream.h temps.h timelog.h times.h token.h unistring.h utils.h value.h views.h xact.h strptime.h ${PROJECT_BINARY_DIR}/system.hh) if (CMAKE_BUILD_TYPE STREQUAL "Debug") if ((CMAKE_CXX_COMPILER MATCHES "clang") OR (CMAKE_CXX_COMPILER MATCHES "cxx")) add_definitions( -Weverything -Wno-disabled-macro-expansion -Wno-padded -Wno-weak-vtables -Wno-exit-time-destructors -Wno-global-constructors -Wno-switch-enum -Wno-missing-prototypes -Wno-missing-noreturn -Wno-unused-parameter -Wno-c++98-compat -fno-limit-debug-info) macro(ADD_PCH_RULE _header_filename _src_list _other_srcs) set(_pch_filename "${_header_filename}.pch") set_source_files_properties( ${${_src_list}} PROPERTIES COMPILE_FLAGS "-include ${_header_filename}") if (_other_srcs) set_source_files_properties( ${_other_srcs} PROPERTIES COMPILE_FLAGS "-include ${_header_filename}") endif() list(APPEND ${_src_list} ${_pch_filename}) set(_args ${CMAKE_CXX_FLAGS}) list(APPEND _args ${CMAKE_CXX_FLAGS_DEBUG}) if (BUILD_LIBRARY) list(APPEND _args ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}) endif() list(APPEND _args "-std=c++11 ") if (CYGWIN) list(APPEND _args "-U__STRICT_ANSI__") endif() list(APPEND _args "-x c++-header " ${_inc}) list(APPEND _args -c ${_header_filename} -o ${_pch_filename}) get_directory_property(DIRINC INCLUDE_DIRECTORIES) foreach(_inc ${DIRINC}) list(APPEND _args "-isystem " ${_inc}) endforeach(_inc ${DIRINC}) separate_arguments(_args) add_custom_command(OUTPUT ${_pch_filename} COMMAND rm -f ${_pch_filename} COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1} ${_args} DEPENDS ${_header_filename}) endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs) elseif(CMAKE_CXX_COMPILER MATCHES "g\\+\\+") set(GXX_WARNING_FLAGS -pedantic -Wall -Winvalid-pch -Wextra -Wcast-align -Wcast-qual -Wfloat-equal -Wmissing-field-initializers -Wno-endif-labels -Wno-overloaded-virtual -Wsign-compare -Wsign-promo -Wwrite-strings -Wno-unused-parameter -Wno-old-style-cast -Wno-deprecated -Wno-strict-aliasing) add_definitions(${GXX_WARNING_FLAGS}) macro(ADD_PCH_RULE _header_filename _src_list _other_srcs) set(_gch_filename "${_header_filename}.gch") set_source_files_properties( ${${_src_list}} PROPERTIES COMPILE_FLAGS "-Winvalid-pch") if (_other_srcs) set_source_files_properties( ${_other_srcs} PROPERTIES COMPILE_FLAGS "-Winvalid-pch") endif() list(APPEND ${_src_list} ${_gch_filename}) set(_args ${CMAKE_CXX_FLAGS}) list(APPEND _args ${CMAKE_CXX_FLAGS_DEBUG}) if (BUILD_LIBRARY) list(APPEND _args ${CMAKE_SHARED_LIBRARY_CXX_FLAGS}) endif() list(APPEND _args ${GXX_WARNING_FLAGS}) list(APPEND _args "-std=c++11 ") if (CYGWIN) list(APPEND _args "-U__STRICT_ANSI__") endif() list(APPEND _args "-x c++-header " ${_inc}) list(APPEND _args -c ${_header_filename} -o ${_gch_filename}) get_directory_property(DIRINC INCLUDE_DIRECTORIES) foreach(_inc ${DIRINC}) list(APPEND _args "-isystem " ${_inc}) endforeach(_inc ${DIRINC}) separate_arguments(_args) add_custom_command(OUTPUT ${_gch_filename} COMMAND rm -f ${_gch_filename} COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1} ${_args} DEPENDS ${_header_filename}) endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs) else() macro(ADD_PCH_RULE _header_filename _src_list _other_srcs) endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs) endif() else() macro(ADD_PCH_RULE _header_filename _src_list _other_srcs) endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs) endif() add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES LEDGER_CLI_SOURCES) include(GNUInstallDirs) if (BUILD_LIBRARY) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") add_library(libledger SHARED ${LEDGER_SOURCES}) add_ledger_library_dependencies(libledger) set_target_properties(libledger PROPERTIES PREFIX "" INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" VERSION ${Ledger_VERSION_MAJOR} SOVERSION ${Ledger_VERSION_MAJOR}) add_executable(ledger main.cc global.cc) target_link_libraries(ledger libledger) if (CMAKE_SYSTEM_NAME STREQUAL Darwin AND HAVE_BOOST_PYTHON) target_link_libraries(ledger ${PYTHON_LIBRARIES}) endif() install(TARGETS libledger DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES ${LEDGER_INCLUDES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ledger) else() add_executable(ledger ${LEDGER_SOURCES} main.cc global.cc) add_ledger_library_dependencies(ledger) endif() if (USE_PYTHON) execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from __future__ import print_function import distutils.sysconfig as s print(s.get_python_lib(True, prefix=''))" OUTPUT_VARIABLE _TMP_PYTHON_SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE) set(PYTHON_SITE_PACKAGES ${_TMP_PYTHON_SITE_PACKAGES} CACHE PATH "python module directory (${_TMP_PYTHON_SITE_PACKAGES})") if (PYTHON_SITE_PACKAGES) if (WIN32 AND NOT CYGWIN) set(_ledger_python_module_name "ledger.pyd") elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin) set(_ledger_python_module_name "ledger.so") else() set(_ledger_python_module_name "ledger${CMAKE_SHARED_LIBRARY_SUFFIX}") endif() # FIXME: symlink would be sufficient: # maybe using install(CODE "...") and # execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink ...). # Windows will need a special case due to not supporting symlinks. add_custom_command( TARGET libledger POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ "${CMAKE_BINARY_DIR}/${_ledger_python_module_name}") install( FILES "${CMAKE_BINARY_DIR}/${_ledger_python_module_name}" DESTINATION ${PYTHON_SITE_PACKAGES}) else() message(WARNING "PYTHON_SITE_PACKAGES not set. Will not install python module.") endif() endif() install(TARGETS ledger DESTINATION ${CMAKE_INSTALL_BINDIR}) ### CMakeLists.txt ends here ledger-3.1.1+dfsg1/src/account.cc000066400000000000000000000515221266023441000165130ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "account.h" #include "post.h" #include "xact.h" namespace ledger { account_t::~account_t() { TRACE_DTOR(account_t); foreach (accounts_map::value_type& pair, accounts) { if (! pair.second->has_flags(ACCOUNT_TEMP) || has_flags(ACCOUNT_TEMP)) { checked_delete(pair.second); } } } account_t * account_t::find_account(const string& acct_name, const bool auto_create) { accounts_map::const_iterator i = accounts.find(acct_name); if (i != accounts.end()) return (*i).second; char buf[8192]; string::size_type sep = acct_name.find(':'); assert(sep < 256|| sep == string::npos); const char * first, * rest; if (sep == string::npos) { first = acct_name.c_str(); rest = NULL; } else { std::strncpy(buf, acct_name.c_str(), sep); buf[sep] = '\0'; first = buf; rest = acct_name.c_str() + sep + 1; } account_t * account; i = accounts.find(first); if (i == accounts.end()) { if (! auto_create) return NULL; account = new account_t(this, first); // An account created within a temporary or generated account is itself // temporary or generated, so that the whole tree has the same status. if (has_flags(ACCOUNT_TEMP)) account->add_flags(ACCOUNT_TEMP); if (has_flags(ACCOUNT_GENERATED)) account->add_flags(ACCOUNT_GENERATED); #if DEBUG_ON std::pair result = #endif accounts.insert(accounts_map::value_type(first, account)); #if DEBUG_ON assert(result.second); #endif } else { account = (*i).second; } if (rest) account = account->find_account(rest, auto_create); return account; } namespace { account_t * find_account_re_(account_t * account, const mask_t& regexp) { if (regexp.match(account->fullname())) return account; foreach (accounts_map::value_type& pair, account->accounts) if (account_t * a = find_account_re_(pair.second, regexp)) return a; return NULL; } } account_t * account_t::find_account_re(const string& regexp) { return find_account_re_(this, mask_t(regexp)); } void account_t::add_post(post_t * post) { posts.push_back(post); // Adding a new post changes the possible totals that may have been // computed before. if (xdata_) { xdata_->self_details.gathered = false; xdata_->self_details.calculated = false; xdata_->family_details.gathered = false; xdata_->family_details.calculated = false; } } void account_t::add_deferred_post(const string& uuid, post_t * post) { if (! deferred_posts) deferred_posts = deferred_posts_map_t(); deferred_posts_map_t::iterator i = deferred_posts->find(uuid); if (i == deferred_posts->end()) { posts_list lst; lst.push_back(post); deferred_posts->insert(deferred_posts_map_t::value_type(uuid, lst)); } else { (*i).second.push_back(post); } } void account_t::apply_deferred_posts() { if (deferred_posts) { foreach (deferred_posts_map_t::value_type& pair, *deferred_posts) { foreach (post_t * post, pair.second) post->account->add_post(post); } deferred_posts = none; } // Also apply in child accounts foreach (const accounts_map::value_type& pair, accounts) pair.second->apply_deferred_posts(); } bool account_t::remove_post(post_t * post) { // It's possible that 'post' wasn't yet in this account, but try to // remove it anyway. This can happen if there is an error during // parsing, when the posting knows what it's account is, but // xact_t::finalize has not yet added that posting to the account. posts.remove(post); post->account = NULL; return true; } string account_t::fullname() const { if (! _fullname.empty()) { return _fullname; } else { const account_t * first = this; string fullname = name; while (first->parent) { first = first->parent; if (! first->name.empty()) fullname = first->name + ":" + fullname; } _fullname = fullname; return fullname; } } string account_t::partial_name(bool flat) const { string pname = name; for (const account_t * acct = parent; acct && acct->parent; acct = acct->parent) { if (! flat) { std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY); assert(count > 0); if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY)) break; } pname = acct->name + ":" + pname; } return pname; } std::ostream& operator<<(std::ostream& out, const account_t& account) { out << account.fullname(); return out; } namespace { value_t get_partial_name(call_scope_t& args) { return string_value(args.context() .partial_name(args.has(0) && args.get(0))); } value_t get_account(call_scope_t& args) { // this gets the name account_t& account(args.context()); if (args.has(0)) { account_t * acct = account.parent; for (; acct && acct->parent; acct = acct->parent) ; if (args[0].is_string()) return scope_value(acct->find_account(args.get(0), false)); else if (args[0].is_mask()) return scope_value(acct->find_account_re(args.get(0).str())); else return NULL_VALUE; } else if (args.type_context() == value_t::SCOPE) { return scope_value(&account); } else { return string_value(account.fullname()); } } value_t get_account_base(account_t& account) { return string_value(account.name); } value_t get_amount(account_t& account) { return SIMPLIFIED_VALUE_OR_ZERO(account.amount()); } value_t get_total(account_t& account) { return SIMPLIFIED_VALUE_OR_ZERO(account.total()); } value_t get_subcount(account_t& account) { return long(account.self_details().posts_count); } value_t get_count(account_t& account) { return long(account.family_details().posts_count); } value_t get_cost(account_t&) { throw_(calc_error, _("An account does not have a 'cost' value")); return false; } value_t get_depth(account_t& account) { return long(account.depth); } value_t get_note(account_t& account) { return account.note ? string_value(*account.note) : NULL_VALUE; } value_t ignore(account_t&) { return false; } value_t get_true(account_t&) { return true; } value_t get_addr(account_t& account) { return long(&account); } value_t get_depth_spacer(account_t& account) { std::size_t depth = 0; for (const account_t * acct = account.parent; acct && acct->parent; acct = acct->parent) { std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY); assert(count > 0); if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY)) depth++; } std::ostringstream out; for (std::size_t i = 0; i < depth; i++) out << " "; return string_value(out.str()); } value_t get_latest_cleared(account_t& account) { return account.self_details().latest_cleared_post; } value_t get_earliest(account_t& account) { return account.self_details().earliest_post; } value_t get_earliest_checkin(account_t& account) { return (! account.self_details().earliest_checkin.is_not_a_date_time() ? value_t(account.self_details().earliest_checkin) : NULL_VALUE); } value_t get_latest(account_t& account) { return account.self_details().latest_post; } value_t get_latest_checkout(account_t& account) { return (! account.self_details().latest_checkout.is_not_a_date_time() ? value_t(account.self_details().latest_checkout) : NULL_VALUE); } value_t get_latest_checkout_cleared(account_t& account) { return account.self_details().latest_checkout_cleared; } template value_t get_wrapper(call_scope_t& args) { return (*Func)(args.context()); } value_t get_parent(account_t& account) { return scope_value(account.parent); } value_t fn_any(call_scope_t& args) { account_t& account(args.context()); expr_t::ptr_op_t expr(args.get(0)); foreach (post_t * p, account.posts) { bind_scope_t bound_scope(args, *p); if (expr->calc(bound_scope, args.locus, args.depth).to_boolean()) return true; } return false; } value_t fn_all(call_scope_t& args) { account_t& account(args.context()); expr_t::ptr_op_t expr(args.get(0)); foreach (post_t * p, account.posts) { bind_scope_t bound_scope(args, *p); if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean()) return false; } return true; } } expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind, const string& fn_name) { if (kind != symbol_t::FUNCTION) return NULL; switch (fn_name[0]) { case 'a': if (fn_name[1] == '\0' || fn_name == "amount") return WRAP_FUNCTOR(get_wrapper<&get_amount>); else if (fn_name == "account") return WRAP_FUNCTOR(&get_account); else if (fn_name == "account_base") return WRAP_FUNCTOR(get_wrapper<&get_account_base>); else if (fn_name == "addr") return WRAP_FUNCTOR(get_wrapper<&get_addr>); else if (fn_name == "any") return WRAP_FUNCTOR(&fn_any); else if (fn_name == "all") return WRAP_FUNCTOR(&fn_all); break; case 'c': if (fn_name == "count") return WRAP_FUNCTOR(get_wrapper<&get_count>); else if (fn_name == "cost") return WRAP_FUNCTOR(get_wrapper<&get_cost>); break; case 'd': 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>); break; case 'e': if (fn_name == "earliest") return WRAP_FUNCTOR(get_wrapper<&get_earliest>); else if (fn_name == "earliest_checkin") return WRAP_FUNCTOR(get_wrapper<&get_earliest_checkin>); break; case 'i': if (fn_name == "is_account") return WRAP_FUNCTOR(get_wrapper<&get_true>); else if (fn_name == "is_index") return WRAP_FUNCTOR(get_wrapper<&get_subcount>); break; case 'l': if (fn_name[1] == '\0') return WRAP_FUNCTOR(get_wrapper<&get_depth>); else if (fn_name == "latest_cleared") return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>); else if (fn_name == "latest") return WRAP_FUNCTOR(get_wrapper<&get_latest>); else if (fn_name == "latest_checkout") return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout>); else if (fn_name == "latest_checkout_cleared") return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout_cleared>); break; case 'n': if (fn_name[1] == '\0') return WRAP_FUNCTOR(get_wrapper<&get_subcount>); else if (fn_name == "note") return WRAP_FUNCTOR(get_wrapper<&get_note>); break; case 'p': if (fn_name == "partial_account") return WRAP_FUNCTOR(get_partial_name); else if (fn_name == "parent") return WRAP_FUNCTOR(get_wrapper<&get_parent>); break; case 's': if (fn_name == "subcount") return WRAP_FUNCTOR(get_wrapper<&get_subcount>); break; case 't': if (fn_name == "total") return WRAP_FUNCTOR(get_wrapper<&get_total>); break; case 'u': if (fn_name == "use_direct_amount") return WRAP_FUNCTOR(get_wrapper<&ignore>); break; case 'N': if (fn_name[1] == '\0') return WRAP_FUNCTOR(get_wrapper<&get_count>); break; case 'O': if (fn_name[1] == '\0') return WRAP_FUNCTOR(get_wrapper<&get_total>); break; } return NULL; } bool account_t::valid() const { if (depth > 256) { DEBUG("ledger.validate", "account_t: depth > 256"); return false; } foreach (const accounts_map::value_type& pair, accounts) { if (this == pair.second) { DEBUG("ledger.validate", "account_t: parent refers to itself!"); return false; } if (! pair.second->valid()) { DEBUG("ledger.validate", "account_t: child not valid"); return false; } } return true; } bool account_t::children_with_xdata() const { foreach (const accounts_map::value_type& pair, accounts) if (pair.second->has_xdata() || pair.second->children_with_xdata()) return true; return false; } std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const { std::size_t count = 0; bool grandchildren_visited = false; foreach (const accounts_map::value_type& pair, accounts) if (pair.second->has_xflags(flags) || pair.second->children_with_flags(flags)) count++; // Although no immediately children were visited, if any progeny at all were // visited, it counts as one. if (count == 0 && grandchildren_visited) count = 1; return count; } account_t::xdata_t::details_t& account_t::xdata_t::details_t::operator+=(const details_t& other) { posts_count += other.posts_count; posts_virtuals_count += other.posts_virtuals_count; posts_cleared_count += other.posts_cleared_count; posts_last_7_count += other.posts_last_7_count; posts_last_30_count += other.posts_last_30_count; posts_this_month_count += other.posts_this_month_count; if (! is_valid(earliest_post) || (is_valid(other.earliest_post) && other.earliest_post < earliest_post)) earliest_post = other.earliest_post; if (! is_valid(earliest_cleared_post) || (is_valid(other.earliest_cleared_post) && other.earliest_cleared_post < earliest_cleared_post)) earliest_cleared_post = other.earliest_cleared_post; if (! is_valid(latest_post) || (is_valid(other.latest_post) && other.latest_post > latest_post)) latest_post = other.latest_post; if (! is_valid(latest_cleared_post) || (is_valid(other.latest_cleared_post) && other.latest_cleared_post > latest_cleared_post)) latest_cleared_post = other.latest_cleared_post; filenames.insert(other.filenames.begin(), other.filenames.end()); accounts_referenced.insert(other.accounts_referenced.begin(), other.accounts_referenced.end()); payees_referenced.insert(other.payees_referenced.begin(), other.payees_referenced.end()); return *this; } void account_t::clear_xdata() { xdata_ = none; foreach (accounts_map::value_type& pair, accounts) if (! pair.second->has_flags(ACCOUNT_TEMP)) pair.second->clear_xdata(); } value_t account_t::amount(const optional& expr) const { if (xdata_ && xdata_->has_flags(ACCOUNT_EXT_VISITED)) { posts_list::const_iterator i; if (xdata_->self_details.last_post) i = *xdata_->self_details.last_post; else i = posts.begin(); for (; i != posts.end(); i++) { if ((*i)->xdata().has_flags(POST_EXT_VISITED)) { if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) { (*i)->add_to_value(xdata_->self_details.total, expr); (*i)->xdata().add_flags(POST_EXT_CONSIDERED); } } xdata_->self_details.last_post = i; } if (xdata_->self_details.last_reported_post) i = *xdata_->self_details.last_reported_post; else i = xdata_->reported_posts.begin(); for (; i != xdata_->reported_posts.end(); i++) { if ((*i)->xdata().has_flags(POST_EXT_VISITED)) { if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) { (*i)->add_to_value(xdata_->self_details.total, expr); (*i)->xdata().add_flags(POST_EXT_CONSIDERED); } } xdata_->self_details.last_reported_post = i; } return xdata_->self_details.total; } else { return NULL_VALUE; } } value_t account_t::total(const optional& expr) const { if (! (xdata_ && xdata_->family_details.calculated)) { const_cast(*this).xdata().family_details.calculated = true; value_t temp; foreach (const accounts_map::value_type& pair, accounts) { temp = pair.second->total(expr); if (! temp.is_null()) add_or_set_value(xdata_->family_details.total, temp); } temp = amount(expr); if (! temp.is_null()) add_or_set_value(xdata_->family_details.total, temp); } return xdata_->family_details.total; } const account_t::xdata_t::details_t& account_t::self_details(bool gather_all) const { if (! (xdata_ && xdata_->self_details.gathered)) { const_cast(*this).xdata().self_details.gathered = true; foreach (const post_t * post, posts) xdata_->self_details.update(const_cast(*post), gather_all); } return xdata_->self_details; } const account_t::xdata_t::details_t& account_t::family_details(bool gather_all) const { if (! (xdata_ && xdata_->family_details.gathered)) { const_cast(*this).xdata().family_details.gathered = true; foreach (const accounts_map::value_type& pair, accounts) xdata_->family_details += pair.second->family_details(gather_all); xdata_->family_details += self_details(gather_all); } return xdata_->family_details; } void account_t::xdata_t::details_t::update(post_t& post, bool gather_all) { posts_count++; if (post.has_flags(POST_VIRTUAL)) posts_virtuals_count++; if (gather_all && post.pos) filenames.insert(post.pos->pathname); date_t date = post.date(); if (date.year() == CURRENT_DATE().year() && date.month() == CURRENT_DATE().month()) posts_this_month_count++; if ((CURRENT_DATE() - date).days() <= 30) posts_last_30_count++; if ((CURRENT_DATE() - date).days() <= 7) posts_last_7_count++; if (! is_valid(earliest_post) || post.date() < earliest_post) earliest_post = post.date(); if (! is_valid(latest_post) || post.date() > latest_post) latest_post = post.date(); if (post.checkin && (earliest_checkin.is_not_a_date_time() || *post.checkin < earliest_checkin)) earliest_checkin = *post.checkin; if (post.checkout && (latest_checkout.is_not_a_date_time() || *post.checkout > latest_checkout)) { latest_checkout = *post.checkout; latest_checkout_cleared = post.state() == item_t::CLEARED; } if (post.state() == item_t::CLEARED) { posts_cleared_count++; if (! is_valid(earliest_cleared_post) || post.date() < earliest_cleared_post) earliest_cleared_post = post.date(); if (! is_valid(latest_cleared_post) || post.date() > latest_cleared_post) latest_cleared_post = post.date(); } if (gather_all) { accounts_referenced.insert(post.account->fullname()); payees_referenced.insert(post.payee()); } } void put_account(property_tree::ptree& st, const account_t& acct, function pred) { if (pred(acct)) { std::ostringstream buf; buf.width(sizeof(unsigned long) * 2); buf.fill('0'); buf << std::hex << reinterpret_cast(&acct); st.put(".id", buf.str()); st.put("name", acct.name); st.put("fullname", acct.fullname()); value_t total = acct.amount(); if (! total.is_null()) put_value(st.put("account-amount", ""), total); total = acct.total(); if (! total.is_null()) put_value(st.put("account-total", ""), total); foreach (const accounts_map::value_type& pair, acct.accounts) put_account(st.add("account", ""), *pair.second, pred); } } } // namespace ledger ledger-3.1.1+dfsg1/src/account.h000066400000000000000000000221531266023441000163530ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup data */ /** * @file account.h * @author John Wiegley * * @ingroup data */ #ifndef _ACCOUNT_H #define _ACCOUNT_H #include "scope.h" namespace ledger { class account_t; class xact_t; class post_t; typedef std::list posts_list; typedef std::map accounts_map; typedef std::map deferred_posts_map_t; class account_t : public supports_flags<>, public scope_t { #define ACCOUNT_NORMAL 0x00 // no flags at all, a basic account #define ACCOUNT_KNOWN 0x01 #define ACCOUNT_TEMP 0x02 // account is a temporary object #define ACCOUNT_GENERATED 0x04 // account never actually existed public: account_t * parent; string name; optional note; unsigned short depth; accounts_map accounts; posts_list posts; optional deferred_posts; optional value_expr; mutable string _fullname; #if DOCUMENT_MODEL mutable void * data; #endif account_t(account_t * _parent = NULL, const string& _name = "", const optional& _note = none) : supports_flags<>(), scope_t(), parent(_parent), name(_name), note(_note), depth(static_cast(parent ? parent->depth + 1 : 0)) #if DOCUMENT_MODEL , data(NULL) #endif { TRACE_CTOR(account_t, "account_t *, const string&, const string&"); } account_t(const account_t& other) : supports_flags<>(other.flags()), scope_t(), parent(other.parent), name(other.name), note(other.note), depth(other.depth), accounts(other.accounts) #if DOCUMENT_MODEL , data(NULL) #endif { TRACE_CTOR(account_t, "copy"); } virtual ~account_t(); virtual string description() { return string(_("account ")) + fullname(); } operator string() const { return fullname(); } string fullname() const; string partial_name(bool flat = false) const; void add_account(account_t * acct) { accounts.insert(accounts_map::value_type(acct->name, acct)); } bool remove_account(account_t * acct) { accounts_map::size_type n = accounts.erase(acct->name); return n > 0; } account_t * find_account(const string& name, bool auto_create = true); account_t * find_account_re(const string& regexp); typedef transform_iterator, accounts_map::iterator> accounts_map_seconds_iterator; accounts_map_seconds_iterator accounts_begin() { return make_transform_iterator (accounts.begin(), boost::bind(&accounts_map::value_type::second, _1)); } accounts_map_seconds_iterator accounts_end() { return make_transform_iterator (accounts.end(), boost::bind(&accounts_map::value_type::second, _1)); } void add_post(post_t * post); void add_deferred_post(const string& uuid, post_t * post); void apply_deferred_posts(); bool remove_post(post_t * post); posts_list::iterator posts_begin() { return posts.begin(); } posts_list::iterator posts_end() { return posts.end(); } virtual expr_t::ptr_op_t lookup(const symbol_t::kind_t kind, const string& name); bool valid() const; friend class journal_t; struct xdata_t : public supports_flags<> { #define ACCOUNT_EXT_SORT_CALC 0x01 #define ACCOUNT_EXT_HAS_NON_VIRTUALS 0x02 #define ACCOUNT_EXT_HAS_UNB_VIRTUALS 0x04 #define ACCOUNT_EXT_AUTO_VIRTUALIZE 0x08 #define ACCOUNT_EXT_VISITED 0x10 #define ACCOUNT_EXT_MATCHING 0x20 #define ACCOUNT_EXT_TO_DISPLAY 0x40 #define ACCOUNT_EXT_DISPLAYED 0x80 struct details_t { value_t total; bool calculated; bool gathered; std::size_t posts_count; std::size_t posts_virtuals_count; std::size_t posts_cleared_count; std::size_t posts_last_7_count; std::size_t posts_last_30_count; std::size_t posts_this_month_count; date_t earliest_post; date_t earliest_cleared_post; date_t latest_post; date_t latest_cleared_post; datetime_t earliest_checkin; datetime_t latest_checkout; bool latest_checkout_cleared; std::set filenames; std::set accounts_referenced; std::set payees_referenced; optional last_post; optional last_reported_post; details_t() : calculated(false), gathered(false), posts_count(0), posts_virtuals_count(0), posts_cleared_count(0), posts_last_7_count(0), posts_last_30_count(0), posts_this_month_count(0) { TRACE_CTOR(account_t::xdata_t::details_t, ""); } // A copy copies nothing details_t(const details_t&) : calculated(false), gathered(false), posts_count(0), posts_virtuals_count(0), posts_cleared_count(0), posts_last_7_count(0), posts_last_30_count(0), posts_this_month_count(0) { TRACE_CTOR(account_t::xdata_t::details_t, "copy"); } ~details_t() throw() { TRACE_DTOR(account_t::xdata_t::details_t); } details_t& operator+=(const details_t& other); void update(post_t& post, bool gather_all = false); }; details_t self_details; details_t family_details; posts_list reported_posts; std::list sort_values; xdata_t() : supports_flags<>() { TRACE_CTOR(account_t::xdata_t, ""); } xdata_t(const xdata_t& other) : supports_flags<>(other.flags()), self_details(other.self_details), family_details(other.family_details), sort_values(other.sort_values) { TRACE_CTOR(account_t::xdata_t, "copy"); } ~xdata_t() throw() { TRACE_DTOR(account_t::xdata_t); } }; // This variable holds optional "extended data" which is usually produced // only during reporting, and only for the posting set being reported. // It's a memory-saving measure to delay allocation until the last possible // moment. mutable optional xdata_; bool has_xdata() const { return static_cast(xdata_); } void clear_xdata(); xdata_t& xdata() { if (! xdata_) xdata_ = xdata_t(); return *xdata_; } const xdata_t& xdata() const { assert(xdata_); return *xdata_; } value_t amount(const optional& expr = none) const; value_t total(const optional& expr = none) const; const xdata_t::details_t& self_details(bool gather_all = true) const; const xdata_t::details_t& family_details(bool gather_all = true) const; bool has_xflags(xdata_t::flags_t flags) const { return xdata_ && xdata_->has_flags(flags); } bool children_with_xdata() const; std::size_t children_with_flags(xdata_t::flags_t flags) const; }; std::ostream& operator<<(std::ostream& out, const account_t& account); void put_account(property_tree::ptree& pt, const account_t& acct, function 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) const { return (lhs.fullname().compare(rhs.fullname()) < 0); } }; } // namespace ledger #endif // _ACCOUNT_H ledger-3.1.1+dfsg1/src/amount.cc000066400000000000000000001072721266023441000163660ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "amount.h" #include "commodity.h" #include "annotate.h" #include "pool.h" namespace ledger { bool amount_t::stream_fullstrings = false; #if !defined(THREADSAFE) // These global temporaries are pre-initialized for the sake of // efficiency, and are reused over and over again. static mpz_t temp; static mpq_t tempq; static mpfr_t tempf; static mpfr_t tempfb; static mpfr_t tempfnum; static mpfr_t tempfden; #endif struct amount_t::bigint_t : public supports_flags<> { #define BIGINT_BULK_ALLOC 0x01 #define BIGINT_KEEP_PREC 0x02 mpq_t val; precision_t prec; uint_least32_t refc; #define MP(bigint) ((bigint)->val) bigint_t() : prec(0), refc(1) { mpq_init(val); TRACE_CTOR(bigint_t, ""); } bigint_t(const bigint_t& other) : supports_flags<>(static_cast (other.flags() & ~BIGINT_BULK_ALLOC)), prec(other.prec), refc(1) { mpq_init(val); mpq_set(val, other.val); TRACE_CTOR(bigint_t, "copy"); } ~bigint_t() { TRACE_DTOR(bigint_t); assert(refc == 0); mpq_clear(val); } bool valid() const { if (prec > 1024) { DEBUG("ledger.validate", "amount_t::bigint_t: prec > 1024"); return false; } if (flags() & ~(BIGINT_BULK_ALLOC | BIGINT_KEEP_PREC)) { DEBUG("ledger.validate", "amount_t::bigint_t: flags() & ~(BULK_ALLOC | KEEP_PREC)"); return false; } return true; } }; bool amount_t::is_initialized = false; namespace { void stream_out_mpq(std::ostream& out, mpq_t quant, amount_t::precision_t precision, int zeros_prec = -1, mpfr_rnd_t rnd = GMP_RNDN, const optional& comm = none) { char * buf = NULL; try { #if DEBUG_ON IF_DEBUG("amount.convert") { char * tbuf = mpq_get_str(NULL, 10, quant); DEBUG("amount.convert", "Rational to convert = " << tbuf); std::free(tbuf); } #endif // Convert the rational number to a floating-point, extending the // floating-point to a large enough size to get a precise answer. mp_prec_t num_prec = static_cast(mpz_sizeinbase(mpq_numref(quant), 2)); num_prec += amount_t::extend_by_digits*64; if (num_prec < MPFR_PREC_MIN) num_prec = MPFR_PREC_MIN; DEBUG("amount.convert", "num prec = " << num_prec); mpfr_set_prec(tempfnum, num_prec); mpfr_set_z(tempfnum, mpq_numref(quant), rnd); mp_prec_t den_prec = static_cast(mpz_sizeinbase(mpq_denref(quant), 2)); den_prec += amount_t::extend_by_digits*64; if (den_prec < MPFR_PREC_MIN) den_prec = MPFR_PREC_MIN; DEBUG("amount.convert", "den prec = " << den_prec); mpfr_set_prec(tempfden, den_prec); mpfr_set_z(tempfden, mpq_denref(quant), rnd); mpfr_set_prec(tempfb, num_prec + den_prec); mpfr_div(tempfb, tempfnum, tempfden, rnd); if (mpfr_asprintf(&buf, "%.*RNf", precision, tempfb) < 0) throw_(amount_error, _("Cannot output amount to a floating-point representation")); DEBUG("amount.convert", "mpfr_print = " << buf << " (precision " << precision << ", zeros_prec " << zeros_prec << ")"); if (zeros_prec >= 0) { string::size_type index = std::strlen(buf); string::size_type point = 0; for (string::size_type i = 0; i < index; i++) { if (buf[i] == '.') { point = i; break; } } if (point > 0) { while (--index >= (point + 1 + static_cast(zeros_prec)) && buf[index] == '0') buf[index] = '\0'; if (index >= (point + static_cast(zeros_prec)) && buf[index] == '.') buf[index] = '\0'; } } if (comm) { int integer_digits = 0; if (comm && comm->has_flags(COMMODITY_STYLE_THOUSANDS)) { // Count the number of integer digits for (const char * p = buf; *p; p++) { if (*p == '.') break; else if (*p != '-') integer_digits++; } } for (const char * p = buf; *p; p++) { if (*p == '.') { if (("h" == comm->symbol() || "m" == comm->symbol()) && (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 out << *p; assert(integer_digits <= 3); } else if (*p == '-') { out << *p; } else { out << *p; if (integer_digits > 3 && --integer_digits % 3 == 0) { if (("h" == comm->symbol() || "m" == comm->symbol()) && (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 out << ','; } } } } else { out << buf; } } catch (...) { if (buf != NULL) mpfr_free_str(buf); throw; } if (buf != NULL) mpfr_free_str(buf); } } void amount_t::initialize() { if (! is_initialized) { mpz_init(temp); mpq_init(tempq); mpfr_init(tempf); mpfr_init(tempfb); mpfr_init(tempfnum); mpfr_init(tempfden); commodity_pool_t::current_pool.reset(new commodity_pool_t); // Add time commodity conversions, so that timelog's may be parsed // in terms of seconds, but reported as minutes or hours. if (commodity_t * commodity = commodity_pool_t::current_pool->create("s")) commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); #if !NO_ASSERTS else assert(false); #endif // Add a "percentile" commodity if (commodity_t * commodity = commodity_pool_t::current_pool->create("%")) commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); #if !NO_ASSERTS else assert(false); #endif is_initialized = true; } } void amount_t::shutdown() { if (is_initialized) { mpz_clear(temp); mpq_clear(tempq); mpfr_clear(tempf); mpfr_clear(tempfb); mpfr_clear(tempfnum); mpfr_clear(tempfden); commodity_pool_t::current_pool.reset(); is_initialized = false; } } void amount_t::_copy(const amount_t& amt) { VERIFY(amt.valid()); if (quantity != amt.quantity) { if (quantity) _release(); // Never maintain a pointer into a bulk allocation pool; such // pointers are not guaranteed to remain. if (amt.quantity->has_flags(BIGINT_BULK_ALLOC)) { quantity = new bigint_t(*amt.quantity); } else { quantity = amt.quantity; DEBUG("amount.refs", quantity << " refc++, now " << (quantity->refc + 1)); quantity->refc++; } } commodity_ = amt.commodity_; VERIFY(valid()); } void amount_t::_dup() { VERIFY(valid()); if (quantity->refc > 1) { bigint_t * q = new bigint_t(*quantity); _release(); quantity = q; } VERIFY(valid()); } void amount_t::_clear() { if (quantity) { _release(); quantity = NULL; commodity_ = NULL; } else { assert(! commodity_); } } void amount_t::_release() { VERIFY(valid()); DEBUG("amount.refs", quantity << " refc--, now " << (quantity->refc - 1)); if (--quantity->refc == 0) { if (quantity->has_flags(BIGINT_BULK_ALLOC)) quantity->~bigint_t(); else checked_delete(quantity); quantity = NULL; commodity_ = NULL; } VERIFY(valid()); } amount_t::amount_t(const double val) : commodity_(NULL) { quantity = new bigint_t; mpq_set_d(MP(quantity), val); quantity->prec = extend_by_digits; // an approximation TRACE_CTOR(amount_t, "const double"); } amount_t::amount_t(const unsigned long val) : commodity_(NULL) { quantity = new bigint_t; mpq_set_ui(MP(quantity), val, 1); TRACE_CTOR(amount_t, "const unsigned long"); } amount_t::amount_t(const long val) : commodity_(NULL) { quantity = new bigint_t; mpq_set_si(MP(quantity), val, 1); TRACE_CTOR(amount_t, "const long"); } amount_t& amount_t::operator=(const amount_t& amt) { if (this != &amt) { if (amt.quantity) _copy(amt); else if (quantity) _clear(); } return *this; } int amount_t::compare(const amount_t& amt) const { VERIFY(amt.valid()); if (! quantity || ! amt.quantity) { if (quantity) throw_(amount_error, _("Cannot compare an amount to an uninitialized amount")); else if (amt.quantity) throw_(amount_error, _("Cannot compare an uninitialized amount to an amount")); else throw_(amount_error, _("Cannot compare two uninitialized amounts")); } if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) { throw_(amount_error, _f("Cannot compare amounts with different commodities: '%1%' and '%2%'") % commodity() % amt.commodity()); } return mpq_cmp(MP(quantity), MP(amt.quantity)); } bool amount_t::operator==(const amount_t& amt) const { if ((quantity && ! amt.quantity) || (! quantity && amt.quantity)) return false; else if (! quantity && ! amt.quantity) return true; else if (commodity() != amt.commodity()) return false; return mpq_equal(MP(quantity), MP(amt.quantity)); } amount_t& amount_t::operator+=(const amount_t& amt) { VERIFY(amt.valid()); if (! quantity || ! amt.quantity) { if (quantity) throw_(amount_error, _("Cannot add an uninitialized amount to an amount")); else if (amt.quantity) throw_(amount_error, _("Cannot add an amount to an uninitialized amount")); else throw_(amount_error, _("Cannot add two uninitialized amounts")); } if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) { throw_(amount_error, _f("Adding amounts with different commodities: '%1%' != '%2%'") % commodity() % amt.commodity()); } _dup(); mpq_add(MP(quantity), MP(quantity), MP(amt.quantity)); if (has_commodity() == amt.has_commodity()) if (quantity->prec < amt.quantity->prec) quantity->prec = amt.quantity->prec; return *this; } amount_t& amount_t::operator-=(const amount_t& amt) { VERIFY(amt.valid()); if (! quantity || ! amt.quantity) { if (quantity) throw_(amount_error, _("Cannot subtract an amount from an uninitialized amount")); else if (amt.quantity) throw_(amount_error, _("Cannot subtract an uninitialized amount from an amount")); else throw_(amount_error, _("Cannot subtract two uninitialized amounts")); } if (has_commodity() && amt.has_commodity() && commodity() != amt.commodity()) { throw_(amount_error, _f("Subtracting amounts with different commodities: '%1%' != '%2%'") % commodity() % amt.commodity()); } _dup(); mpq_sub(MP(quantity), MP(quantity), MP(amt.quantity)); if (has_commodity() == amt.has_commodity()) if (quantity->prec < amt.quantity->prec) quantity->prec = amt.quantity->prec; return *this; } amount_t& amount_t::multiply(const amount_t& amt, bool ignore_commodity) { VERIFY(amt.valid()); if (! quantity || ! amt.quantity) { if (quantity) throw_(amount_error, _("Cannot multiply an amount by an uninitialized amount")); else if (amt.quantity) throw_(amount_error, _("Cannot multiply an uninitialized amount by an amount")); else throw_(amount_error, _("Cannot multiply two uninitialized amounts")); } _dup(); mpq_mul(MP(quantity), MP(quantity), MP(amt.quantity)); quantity->prec = static_cast(quantity->prec + amt.quantity->prec); if (! has_commodity() && ! ignore_commodity) commodity_ = amt.commodity_; if (has_commodity() && ! keep_precision()) { precision_t comm_prec = commodity().precision(); if (quantity->prec > comm_prec + extend_by_digits) quantity->prec = static_cast(comm_prec + extend_by_digits); } return *this; } amount_t& amount_t::operator/=(const amount_t& amt) { VERIFY(amt.valid()); if (! quantity || ! amt.quantity) { if (quantity) throw_(amount_error, _("Cannot divide an amount by an uninitialized amount")); else if (amt.quantity) throw_(amount_error, _("Cannot divide an uninitialized amount by an amount")); else throw_(amount_error, _("Cannot divide two uninitialized amounts")); } if (! amt) throw_(amount_error, _("Divide by zero")); _dup(); // Increase the value's precision, to capture fractional parts after // the divide. Round up in the last position. mpq_div(MP(quantity), MP(quantity), MP(amt.quantity)); quantity->prec = static_cast(quantity->prec + amt.quantity->prec + extend_by_digits); if (! has_commodity()) commodity_ = amt.commodity_; // If this amount has a commodity, and we're not dealing with plain // numbers, or internal numbers (which keep full precision at all // times), then round the number to within the commodity's precision // plus six places. if (has_commodity() && ! keep_precision()) { precision_t comm_prec = commodity().precision(); if (quantity->prec > comm_prec + extend_by_digits) quantity->prec = static_cast(comm_prec + extend_by_digits); } return *this; } amount_t::precision_t amount_t::precision() const { if (! quantity) throw_(amount_error, _("Cannot determine precision of an uninitialized amount")); return quantity->prec; } bool amount_t::keep_precision() const { if (! quantity) throw_(amount_error, _("Cannot determine if precision of an uninitialized amount is kept")); return quantity->has_flags(BIGINT_KEEP_PREC); } void amount_t::set_keep_precision(const bool keep) const { if (! quantity) throw_(amount_error, _("Cannot set whether to keep the precision of an uninitialized amount")); if (keep) quantity->add_flags(BIGINT_KEEP_PREC); else quantity->drop_flags(BIGINT_KEEP_PREC); } amount_t::precision_t amount_t::display_precision() const { if (! quantity) throw_(amount_error, _("Cannot determine display precision of an uninitialized amount")); commodity_t& comm(commodity()); if (comm && ! keep_precision()) return comm.precision(); else return comm ? std::max(quantity->prec, comm.precision()) : quantity->prec; } void amount_t::in_place_negate() { if (quantity) { _dup(); mpq_neg(MP(quantity), MP(quantity)); } else { throw_(amount_error, _("Cannot negate an uninitialized amount")); } } void amount_t::in_place_invert() { if (! quantity) throw_(amount_error, _("Cannot invert an uninitialized amount")); _dup(); mpq_inv(MP(quantity), MP(quantity)); } void amount_t::in_place_round() { if (! quantity) throw_(amount_error, _("Cannot set rounding for an uninitialized amount")); else if (! keep_precision()) return; _dup(); set_keep_precision(false); } void amount_t::in_place_truncate() { #if 1 if (! quantity) throw_(amount_error, _("Cannot truncate an uninitialized amount")); _dup(); DEBUG("amount.truncate", "Truncating " << *this << " to precision " << display_precision()); std::ostringstream out; stream_out_mpq(out, MP(quantity), display_precision()); scoped_array buf(new char [out.str().length() + 1]); std::strcpy(buf.get(), out.str().c_str()); char * q = buf.get(); for (char * p = q; *p != '\0'; p++, q++) { if (*p == '.') p++; if (p != q) *q = *p; } *q = '\0'; mpq_set_str(MP(quantity), buf.get(), 10); mpz_ui_pow_ui(temp, 10, display_precision()); mpq_set_z(tempq, temp); mpq_div(MP(quantity), MP(quantity), tempq); DEBUG("amount.truncate", "Truncated = " << *this); #else // This naive implementation is straightforward, but extremely inefficient // as it requires parsing the commodity too, which might be fully annotated. *this = amount_t(to_string()); #endif } void amount_t::in_place_floor() { if (! quantity) throw_(amount_error, _("Cannot compute floor on an uninitialized amount")); _dup(); 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(); 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() { if (! quantity) throw_(amount_error, _("Cannot unround an uninitialized amount")); else if (keep_precision()) return; _dup(); DEBUG("amount.unround", "Unrounding " << *this); set_keep_precision(true); DEBUG("amount.unround", "Unrounded = " << *this); } void amount_t::in_place_reduce() { if (! quantity) throw_(amount_error, _("Cannot reduce an uninitialized amount")); while (commodity_ && commodity().smaller()) { *this *= commodity().smaller()->number(); commodity_ = commodity().smaller()->commodity_; } } void amount_t::in_place_unreduce() { if (! quantity) throw_(amount_error, _("Cannot unreduce an uninitialized amount")); amount_t tmp = *this; commodity_t * comm = commodity_; bool shifted = false; while (comm && comm->larger()) { amount_t next_temp = tmp / comm->larger()->number(); if (next_temp.abs() < amount_t(1L)) break; tmp = next_temp; comm = comm->larger()->commodity_; shifted = true; } if (shifted) { if (("h" == comm->symbol() || "m" == 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; } } optional amount_t::value(const datetime_t& moment, const commodity_t * in_terms_of) const { if (quantity) { #if DEBUG_ON DEBUG("commodity.price.find", "amount_t::value of " << commodity().symbol()); if (! moment.is_not_a_date_time()) DEBUG("commodity.price.find", "amount_t::value: moment = " << moment); if (in_terms_of) DEBUG("commodity.price.find", "amount_t::value: in_terms_of = " << in_terms_of->symbol()); #endif if (has_commodity() && (in_terms_of || ! commodity().has_flags(COMMODITY_PRIMARY))) { optional point; const commodity_t * comm(in_terms_of); if (has_annotation() && annotation().price) { if (annotation().has_flags(ANNOTATION_PRICE_FIXATED)) { point = price_point_t(); point->price = *annotation().price; DEBUG("commodity.prices.find", "amount_t::value: fixated price = " << point->price); } else if (! comm) { comm = annotation().price->commodity_ptr(); } } if (comm && commodity().referent() == comm->referent()) return with_commodity(comm->referent()); if (! point) { point = commodity().find_price(comm, moment); // Whether a price was found or not, check whether we should attempt // to download a price from the Internet. This is done if (a) no // price was found, or (b) the price is "stale" according to the // setting of --price-exp. if (point) point = commodity().check_for_updated_price(point, moment, comm); } if (point) { amount_t price(point->price); price.multiply(*this, true); price.in_place_round(); return price; } } } else { throw_(amount_error, _("Cannot determine value of an uninitialized amount")); } return none; } optional amount_t::price() const { if (has_annotation() && annotation().price) { amount_t tmp(*annotation().price); tmp *= *this; DEBUG("amount.price", "Returning price of " << *this << " = " << tmp); return tmp; } return none; } int amount_t::sign() const { if (! quantity) throw_(amount_error, _("Cannot determine sign of an uninitialized amount")); return mpq_sgn(MP(quantity)); } bool amount_t::is_zero() const { if (! quantity) throw_(amount_error, _("Cannot determine if an uninitialized amount is zero")); if (has_commodity()) { if (keep_precision() || quantity->prec <= commodity().precision()) { return is_realzero(); } else if (is_realzero()) { return true; } else if (mpz_cmp(mpq_numref(MP(quantity)), mpq_denref(MP(quantity))) > 0) { DEBUG("amount.is_zero", "Numerator is larger than the denominator"); return false; } else { DEBUG("amount.is_zero", "We have to print the number to check for zero"); std::ostringstream out; stream_out_mpq(out, MP(quantity), commodity().precision()); string output = out.str(); if (! output.empty()) { for (const char * p = output.c_str(); *p; p++) if (*p != '0' && *p != '.' && *p != '-') return false; } return true; } } return is_realzero(); } double amount_t::to_double() const { if (! quantity) throw_(amount_error, _("Cannot convert an uninitialized amount to a double")); mpfr_set_q(tempf, MP(quantity), GMP_RNDN); return mpfr_get_d(tempf, GMP_RNDN); } long amount_t::to_long() const { if (! quantity) throw_(amount_error, _("Cannot convert an uninitialized amount to a long")); mpfr_set_q(tempf, MP(quantity), GMP_RNDN); return mpfr_get_si(tempf, GMP_RNDN); } bool amount_t::fits_in_long() const { mpfr_set_q(tempf, MP(quantity), GMP_RNDN); return mpfr_fits_slong_p(tempf, GMP_RNDN); } commodity_t * amount_t::commodity_ptr() const { return (commodity_ ? commodity_ : commodity_pool_t::current_pool->null_commodity); } bool amount_t::has_commodity() const { return commodity_ && commodity_ != commodity_->pool().null_commodity; } void amount_t::annotate(const annotation_t& details) { commodity_t * this_base; annotated_commodity_t * this_ann = NULL; if (! quantity) throw_(amount_error, _("Cannot annotate the commodity of an uninitialized amount")); else if (! has_commodity()) return; // ignore attempt to annotate a "bare commodity if (commodity().has_annotation()) { this_ann = &as_annotated_commodity(commodity()); this_base = &this_ann->referent(); } else { this_base = &commodity(); } assert(this_base); DEBUG("amount.commodities", "Annotating commodity for amount " << *this << std::endl << details); if (commodity_t * ann_comm = this_base->pool().find_or_create(*this_base, details)) set_commodity(*ann_comm); #if !NO_ASSERTS else assert(false); #endif DEBUG("amount.commodities", "Annotated amount is " << *this); } bool amount_t::has_annotation() const { if (! quantity) throw_(amount_error, _("Cannot determine if an uninitialized amount's commodity is annotated")); assert(! has_commodity() || ! commodity().has_annotation() || as_annotated_commodity(commodity()).details); return has_commodity() && commodity().has_annotation(); } annotation_t& amount_t::annotation() { if (! quantity) throw_(amount_error, _("Cannot return commodity annotation details of an uninitialized amount")); if (! commodity().has_annotation()) throw_(amount_error, _("Request for annotation details from an unannotated amount")); annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); return ann_comm.details; } amount_t amount_t::strip_annotations(const keep_details_t& what_to_keep) const { if (! quantity) throw_(amount_error, _("Cannot strip commodity annotations from an uninitialized amount")); if (! what_to_keep.keep_all(commodity())) { amount_t t(*this); t.set_commodity(commodity().strip_annotations(what_to_keep)); return t; } return *this; } namespace { void parse_quantity(std::istream& in, string& value) { char buf[256]; char c = peek_next_nonws(in); READ_INTO(in, buf, 255, c, std::isdigit(c) || c == '-' || c == '.' || c == ','); string::size_type len = std::strlen(buf); while (len > 0 && ! std::isdigit(buf[len - 1])) { buf[--len] = '\0'; in.unget(); } value = buf; } } bool amount_t::parse(std::istream& in, const parse_flags_t& flags) { // The possible syntax for an amount is: // // [-]NUM[ ]SYM [@ AMOUNT] // SYM[ ][-]NUM [@ AMOUNT] string symbol; string quant; annotation_t details; bool negative = false; commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS; char c = peek_next_nonws(in); if (c == '-') { negative = true; in.get(c); c = peek_next_nonws(in); } char n; if (std::isdigit(c)) { parse_quantity(in, quant); if (! in.eof() && ((n = static_cast(in.peek())) != '\n')) { if (std::isspace(n)) comm_flags |= COMMODITY_STYLE_SEPARATED; commodity_t::parse_symbol(in, symbol); if (! symbol.empty()) comm_flags |= COMMODITY_STYLE_SUFFIXED; if (! flags.has_flags(PARSE_NO_ANNOT) && ! in.eof() && ((n = static_cast(in.peek())) != '\n')) details.parse(in); } } else { commodity_t::parse_symbol(in, symbol); if (! in.eof() && ((n = static_cast(in.peek())) != '\n')) { if (std::isspace(static_cast(in.peek()))) comm_flags |= COMMODITY_STYLE_SEPARATED; parse_quantity(in, quant); if (! flags.has_flags(PARSE_NO_ANNOT) && ! quant.empty() && ! in.eof() && ((n = static_cast(in.peek())) != '\n')) details.parse(in); } } if (quant.empty()) { if (flags.has_flags(PARSE_SOFT_FAIL)) return false; else throw_(amount_error, _("No quantity specified for amount")); } // Allocate memory for the amount's quantity value. We have to // monitor the allocation in a unique_ptr because this function gets // called sometimes from amount_t's constructor; and if there is an // exeception thrown by any of the function calls after this point, // the destructor will never be called and the memory never freed. unique_ptr new_quantity; if (quantity) { if (quantity->refc > 1) _release(); else new_quantity.reset(quantity); quantity = NULL; } if (! new_quantity.get()) new_quantity.reset(new bigint_t); // No one is holding a reference to this now. new_quantity->refc--; // Create the commodity if has not already been seen, and update the // precision if something greater was used for the quantity. if (symbol.empty()) { commodity_ = NULL; } else { commodity_ = commodity_pool_t::current_pool->find(symbol); if (! commodity_) commodity_ = commodity_pool_t::current_pool->create(symbol); assert(commodity_); } // Quickly scan through and verify the correctness of the amount's use of // punctuation. precision_t decimal_offset = 0; string::size_type string_index = quant.length(); string::size_type last_comma = string::npos; string::size_type last_period = string::npos; bool no_more_commas = false; bool no_more_periods = false; bool decimal_comma_style = (commodity_t::decimal_comma_by_default || commodity().has_flags(COMMODITY_STYLE_DECIMAL_COMMA)); #if 0 bool time_colon_style = (commodity_t::time_colon_by_default || commodity().has_flags(COMMODITY_STYLE_TIME_COLON)); #endif new_quantity->prec = 0; BOOST_REVERSE_FOREACH (const char& ch, quant) { string_index--; if (ch == '.') { if (no_more_periods) throw_(amount_error, _("Too many periods in amount")); if (decimal_comma_style) { if (decimal_offset % 3 != 0) throw_(amount_error, _("Incorrect use of thousand-mark period")); comm_flags |= COMMODITY_STYLE_THOUSANDS; no_more_commas = true; } else { if (last_comma != string::npos) { decimal_comma_style = true; if (decimal_offset % 3 != 0) throw_(amount_error, _("Incorrect use of thousand-mark period")); } else { no_more_periods = true; new_quantity->prec = decimal_offset; decimal_offset = 0; } } if (last_period == string::npos) last_period = string_index; } else if (ch == ',') { if (no_more_commas) throw_(amount_error, _("Too many commas in amount")); if (decimal_comma_style) { if (last_period != string::npos) { throw_(amount_error, _("Incorrect use of decimal comma")); } else { no_more_commas = true; new_quantity->prec = decimal_offset; decimal_offset = 0; } } else { if (decimal_offset % 3 != 0) { if (last_comma != string::npos || last_period != string::npos) { throw_(amount_error, _("Incorrect use of thousand-mark comma")); } else { decimal_comma_style = true; no_more_commas = true; new_quantity->prec = decimal_offset; decimal_offset = 0; } } else { comm_flags |= COMMODITY_STYLE_THOUSANDS; no_more_periods = true; } } if (last_comma == string::npos) last_comma = string_index; } else { decimal_offset++; } } if (decimal_comma_style) comm_flags |= COMMODITY_STYLE_DECIMAL_COMMA; if (flags.has_flags(PARSE_NO_MIGRATE)) { // Can't call set_keep_precision here, because it assumes that `quantity' // is non-NULL. new_quantity->add_flags(BIGINT_KEEP_PREC); } else if (commodity_) { commodity().add_flags(comm_flags); if (new_quantity->prec > commodity().precision()) commodity().set_precision(new_quantity->prec); } // Now we have the final number. Remove commas and periods, if necessary. if (last_comma != string::npos || last_period != string::npos) { string::size_type len = quant.length(); scoped_array buf(new char[len + 1]); const char * p = quant.c_str(); char * t = buf.get(); while (*p) { if (*p == ',' || *p == '.') p++; *t++ = *p++; } *t = '\0'; mpq_set_str(MP(new_quantity.get()), buf.get(), 10); mpz_ui_pow_ui(temp, 10, new_quantity->prec); mpq_set_z(tempq, temp); mpq_div(MP(new_quantity.get()), MP(new_quantity.get()), tempq); IF_DEBUG("amount.parse") { char * amt_buf = mpq_get_str(NULL, 10, MP(new_quantity.get())); DEBUG("amount.parse", "Rational parsed = " << amt_buf); std::free(amt_buf); } } else { mpq_set_str(MP(new_quantity.get()), quant.c_str(), 10); } if (negative) mpq_neg(MP(new_quantity.get()), MP(new_quantity.get())); new_quantity->refc++; quantity = new_quantity.release(); if (! flags.has_flags(PARSE_NO_REDUCE)) in_place_reduce(); // will not throw an exception if (commodity_ && details) { if (details.has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) { assert(details.price); *details.price /= this->abs(); } set_commodity(*commodity_pool_t::current_pool->find_or_create(*commodity_, details)); } VERIFY(valid()); return true; } void amount_t::parse_conversion(const string& larger_str, const string& smaller_str) { amount_t larger, smaller; larger.parse(larger_str, PARSE_NO_REDUCE); smaller.parse(smaller_str, PARSE_NO_REDUCE); larger *= smaller.number(); if (larger.commodity()) { larger.commodity().set_smaller(smaller); larger.commodity().add_flags(smaller.commodity().flags() | COMMODITY_NOMARKET); } if (smaller.commodity()) smaller.commodity().set_larger(larger); } void amount_t::print(std::ostream& _out, const uint_least8_t flags) const { VERIFY(valid()); if (! quantity) { _out << ""; return; } std::ostringstream out; commodity_t& comm(commodity()); if (! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { comm.print(out, flags & AMOUNT_PRINT_ELIDE_COMMODITY_QUOTES); if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << " "; } stream_out_mpq(out, MP(quantity), display_precision(), comm ? commodity().precision() : 0, GMP_RNDN, comm); if (comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) out << " "; comm.print(out, flags & AMOUNT_PRINT_ELIDE_COMMODITY_QUOTES); } // If there are any annotations associated with this commodity, output them // now. comm.write_annotations(out, flags & AMOUNT_PRINT_NO_COMPUTED_ANNOTATIONS); // Things are output to a string first, so that if anyone has specified a // width or fill for _out, it will be applied to the entire amount string, // and not just the first part. _out << out.str(); } bool amount_t::valid() const { if (quantity) { if (! quantity->valid()) { DEBUG("ledger.validate", "amount_t: ! quantity->valid()"); return false; } if (quantity->refc == 0) { DEBUG("ledger.validate", "amount_t: quantity->refc == 0"); return false; } } else if (commodity_) { DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); return false; } return true; } void put_amount(property_tree::ptree& st, const amount_t& amt, bool commodity_details) { if (amt.has_commodity()) put_commodity(st.put("commodity", ""), amt.commodity(), commodity_details); st.put("quantity", amt.quantity_string()); } } // namespace ledger ledger-3.1.1+dfsg1/src/amount.h000066400000000000000000000616611266023441000162310ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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_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 > > > { 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 bool operator==(const T& val) const { return compare(val) == 0; } template bool operator<(const T& amt) const { return compare(amt) < 0; } template 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(); 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 { 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 10 AAPL, 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 value(const datetime_t& moment = datetime_t(), const commodity_t * in_terms_of = NULL) const; optional 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(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(*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 20 AAPL. 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; /*@}*/ }; 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 commodity_details = false); } // namespace ledger #endif // _AMOUNT_H ledger-3.1.1+dfsg1/src/annotate.cc000066400000000000000000000263071266023441000166730ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "amount.h" #include "commodity.h" #include "expr.h" #include "annotate.h" #include "pool.h" namespace ledger { bool annotation_t::operator<(const annotation_t& rhs) const { if (! price && rhs.price) return true; if (price && ! rhs.price) return false; if (! date && rhs.date) return true; if (date && ! rhs.date) return false; if (! tag && rhs.tag) return true; if (tag && ! rhs.tag) return false; if (! value_expr && rhs.value_expr) return true; if (value_expr && ! rhs.value_expr) return false; if (price) { if (price->commodity().symbol() < rhs.price->commodity().symbol()) return true; if (price->commodity().symbol() > rhs.price->commodity().symbol()) return false; if (*price < *rhs.price) return true; if (*price > *rhs.price) return false; } if (date) { if (*date < *rhs.date) return true; if (*date > *rhs.date) return false; } if (tag) { if (*tag < *rhs.tag) return true; if (*tag > *rhs.tag) return false; } if (value_expr) { DEBUG("annotate.less", "Comparing (" << value_expr->text() << ") < (" << rhs.value_expr->text()); if (value_expr->text() < rhs.value_expr->text()) return true; //if (value_expr->text() > rhs.value_expr->text()) return false; } return false; } void annotation_t::parse(std::istream& in) { do { istream_pos_type pos = in.tellg(); if (static_cast(pos) < 0) return; char buf[256]; char c = peek_next_nonws(in); if (c == '{') { if (price) throw_(amount_error, _("Commodity specifies more than one price")); in.get(c); c = static_cast(in.peek()); if (c == '{') { in.get(c); add_flags(ANNOTATION_PRICE_NOT_PER_UNIT); } c = peek_next_nonws(in); if (c == '=') { in.get(c); add_flags(ANNOTATION_PRICE_FIXATED); } READ_INTO(in, buf, 255, c, c != '}'); if (c == '}') { in.get(c); if (has_flags(ANNOTATION_PRICE_NOT_PER_UNIT)) { c = static_cast(in.peek()); if (c != '}') throw_(amount_error, _("Commodity lot price lacks double closing brace")); else in.get(c); } } else { throw_(amount_error, _("Commodity lot price lacks closing brace")); } amount_t temp; temp.parse(buf, PARSE_NO_MIGRATE); DEBUG("commodity.annotations", "Parsed annotation price: " << temp); price = temp; } else if (c == '[') { if (date) throw_(amount_error, _("Commodity specifies more than one date")); in.get(c); READ_INTO(in, buf, 255, c, c != ']'); if (c == ']') in.get(c); else throw_(amount_error, _("Commodity date lacks closing bracket")); date = parse_date(buf); } else if (c == '(') { in.get(c); c = static_cast(in.peek()); if (c == '@') { in.clear(); in.seekg(pos, std::ios::beg); break; } else if (c == '(') { if (value_expr) throw_(amount_error, _("Commodity specifies more than one valuation expresion")); in.get(c); READ_INTO(in, buf, 255, c, c != ')'); if (c == ')') { in.get(c); c = static_cast(in.peek()); if (c == ')') in.get(c); else throw_(amount_error, _("Commodity valuation expression lacks closing parentheses")); } else { throw_(amount_error, _("Commodity valuation expression lacks closing parentheses")); } value_expr = expr_t(buf); } else { if (tag) throw_(amount_error, _("Commodity specifies more than one tag")); READ_INTO(in, buf, 255, c, c != ')'); if (c == ')') in.get(c); else throw_(amount_error, _("Commodity tag lacks closing parenthesis")); tag = buf; } } else { in.clear(); in.seekg(pos, std::ios::beg); break; } } while (true); #if DEBUG_ON if (SHOW_DEBUG("amount.commodities") && *this) { DEBUG("amount.commodities", "Parsed commodity annotations: " << std::endl << *this); } #endif } void annotation_t::print(std::ostream& out, bool keep_base, bool no_computed_annotations) const { if (price && (! no_computed_annotations || ! has_flags(ANNOTATION_PRICE_CALCULATED))) out << " {" << (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "") << (keep_base ? *price : price->unreduced()) << '}'; if (date && (! no_computed_annotations || ! has_flags(ANNOTATION_DATE_CALCULATED))) out << " [" << format_date(*date, FMT_PRINTED) << ']'; if (tag && (! no_computed_annotations || ! has_flags(ANNOTATION_TAG_CALCULATED))) out << " (" << *tag << ')'; if (value_expr && ! has_flags(ANNOTATION_VALUE_EXPR_CALCULATED)) out << " ((" << *value_expr << "))"; } void put_annotation(property_tree::ptree& st, const annotation_t& details) { if (details.price) put_amount(st.put("price", ""), *details.price); if (details.date) put_date(st.put("date", ""), *details.date); if (details.tag) st.put("tag", *details.tag); if (details.value_expr) st.put("value_expr", details.value_expr->text()); } bool keep_details_t::keep_all(const commodity_t& comm) const { return (! comm.has_annotation() || (keep_price && keep_date && keep_tag && ! only_actuals)); } bool keep_details_t::keep_any(const commodity_t& comm) const { return comm.has_annotation() && (keep_price || keep_date || keep_tag); } bool annotated_commodity_t::operator==(const commodity_t& comm) const { // If the base commodities don't match, the game's up. if (base != comm.base) return false; assert(annotated); if (! comm.annotated) return false; if (details != as_annotated_commodity(comm).details) return false; return true; } optional annotated_commodity_t::find_price(const commodity_t * commodity, const datetime_t& moment, const datetime_t& oldest) const { DEBUG("commodity.price.find", "annotated_commodity_t::find_price(" << symbol() << ")"); datetime_t when; if (! moment.is_not_a_date_time()) when = moment; else if (epoch) when = *epoch; else when = CURRENT_TIME(); DEBUG("commodity.price.find", "reference time: " << when); const commodity_t * target = NULL; if (commodity) target = commodity; if (details.price) { DEBUG("commodity.price.find", "price annotation: " << *details.price); if (details.has_flags(ANNOTATION_PRICE_FIXATED)) { DEBUG("commodity.price.find", "amount_t::value: fixated price = " << *details.price); return price_point_t(when, *details.price); } else if (! target) { DEBUG("commodity.price.find", "setting target commodity from price"); target = details.price->commodity_ptr(); } } #if DEBUG_ON if (target) DEBUG("commodity.price.find", "target commodity: " << target->symbol()); #endif if (details.value_expr) return find_price_from_expr(const_cast(*details.value_expr), commodity, when); return commodity_t::find_price(target, when, oldest); } commodity_t& annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) { DEBUG("commodity.annotated.strip", "Reducing commodity " << *this << std::endl << " keep price " << what_to_keep.keep_price << " " << " keep date " << what_to_keep.keep_date << " " << " keep tag " << what_to_keep.keep_tag); commodity_t * new_comm; bool keep_price = ((what_to_keep.keep_price || (details.has_flags(ANNOTATION_PRICE_FIXATED) && has_flags(COMMODITY_SAW_ANN_PRICE_FLOAT) && has_flags(COMMODITY_SAW_ANN_PRICE_FIXATED))) && (! what_to_keep.only_actuals || ! details.has_flags(ANNOTATION_PRICE_CALCULATED))); bool keep_date = (what_to_keep.keep_date && (! what_to_keep.only_actuals || ! details.has_flags(ANNOTATION_DATE_CALCULATED))); bool keep_tag = (what_to_keep.keep_tag && (! what_to_keep.only_actuals || ! details.has_flags(ANNOTATION_TAG_CALCULATED))); DEBUG("commodity.annotated.strip", "Reducing commodity " << *this << std::endl << " keep price " << keep_price << " " << " keep date " << keep_date << " " << " keep tag " << keep_tag); if ((keep_price && details.price) || (keep_date && details.date) || (keep_tag && details.tag)) { new_comm = pool().find_or_create (referent(), annotation_t(keep_price ? details.price : none, keep_date ? details.date : none, keep_tag ? details.tag : none)); // Transfer over any relevant annotation flags, as they still apply. if (new_comm->annotated) { annotation_t& new_details(as_annotated_commodity(*new_comm).details); if (keep_price) new_details.add_flags(details.flags() & (ANNOTATION_PRICE_CALCULATED | ANNOTATION_PRICE_FIXATED)); if (keep_date) new_details.add_flags(details.flags() & ANNOTATION_DATE_CALCULATED); if (keep_tag) new_details.add_flags(details.flags() & ANNOTATION_TAG_CALCULATED); } return *new_comm; } return referent(); } void annotated_commodity_t::write_annotations (std::ostream& out, bool no_computed_annotations) const { details.print(out, pool().keep_base, no_computed_annotations); } } // namespace ledger ledger-3.1.1+dfsg1/src/annotate.h000066400000000000000000000163061266023441000165330ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup math */ /** * @file annotate.h * @author John Wiegley * * @ingroup math * * @brief Types for annotating commodities * * Long. */ #ifndef _ANNOTATE_H #define _ANNOTATE_H #include "expr.h" namespace ledger { struct annotation_t : public supports_flags<>, public equality_comparable { #define ANNOTATION_PRICE_CALCULATED 0x01 #define ANNOTATION_PRICE_FIXATED 0x02 #define ANNOTATION_PRICE_NOT_PER_UNIT 0x04 #define ANNOTATION_DATE_CALCULATED 0x08 #define ANNOTATION_TAG_CALCULATED 0x10 #define ANNOTATION_VALUE_EXPR_CALCULATED 0x20 optional price; optional date; optional tag; optional value_expr; explicit annotation_t(const optional& _price = none, const optional& _date = none, const optional& _tag = none, const optional& _value_expr = none) : supports_flags<>(), price(_price), date(_date), tag(_tag), value_expr(_value_expr) { TRACE_CTOR(annotation_t, "optional + date_t + string + expr_t"); } annotation_t(const annotation_t& other) : supports_flags<>(other.flags()), price(other.price), date(other.date), tag(other.tag), value_expr(other.value_expr) { TRACE_CTOR(annotation_t, "copy"); } ~annotation_t() { TRACE_DTOR(annotation_t); } operator bool() const { return price || date || tag || value_expr; } bool operator<(const annotation_t& rhs) const; bool operator==(const annotation_t& rhs) const { return (price == rhs.price && date == rhs.date && tag == rhs.tag && (value_expr && rhs.value_expr ? value_expr->text() == rhs.value_expr->text() : value_expr == rhs.value_expr)); } void parse(std::istream& in); void print(std::ostream& out, bool keep_base = false, bool no_computed_annotations = false) const; bool valid() const { assert(*this); return true; } }; void put_annotation(property_tree::ptree& pt, const annotation_t& details); struct keep_details_t { bool keep_price; bool keep_date; bool keep_tag; bool only_actuals; explicit keep_details_t(bool _keep_price = false, bool _keep_date = false, bool _keep_tag = false, bool _only_actuals = false) : keep_price(_keep_price), keep_date(_keep_date), keep_tag(_keep_tag), only_actuals(_only_actuals) { TRACE_CTOR(keep_details_t, "bool, bool, bool, bool"); } keep_details_t(const keep_details_t& other) : keep_price(other.keep_price), keep_date(other.keep_date), keep_tag(other.keep_tag), only_actuals(other.only_actuals) { TRACE_CTOR(keep_details_t, "copy"); } ~keep_details_t() throw() { TRACE_DTOR(keep_details_t); } bool keep_all() const { return keep_price && keep_date && keep_tag && ! only_actuals; } bool keep_all(const commodity_t& comm) const; bool keep_any() const { return keep_price || keep_date || keep_tag; } bool keep_any(const commodity_t& comm) const; }; inline std::ostream& operator<<(std::ostream& out, const annotation_t& details) { details.print(out); return out; } class annotated_commodity_t : public commodity_t, public equality_comparable > { protected: friend class commodity_pool_t; commodity_t * ptr; explicit annotated_commodity_t(commodity_t * _ptr, const annotation_t& _details) : commodity_t(_ptr->parent_, _ptr->base), ptr(_ptr), details(_details) { annotated = true; qualified_symbol = _ptr->qualified_symbol; TRACE_CTOR(annotated_commodity_t, "commodity_t *, annotation_t"); } public: annotation_t details; virtual ~annotated_commodity_t() { TRACE_DTOR(annotated_commodity_t); } virtual bool operator==(const commodity_t& comm) const; virtual bool operator==(const annotated_commodity_t& comm) const { return *this == static_cast(comm); } virtual commodity_t& referent() { return *ptr; } virtual const commodity_t& referent() const { return *ptr; } virtual optional value_expr() const { if (details.value_expr) return details.value_expr; return commodity_t::value_expr(); } optional virtual find_price(const commodity_t * commodity = NULL, const datetime_t& moment = datetime_t(), const datetime_t& oldest = datetime_t()) const; virtual commodity_t& strip_annotations(const keep_details_t& what_to_keep); virtual void print(std::ostream& out, bool elide_quotes = false, bool print_annotations = false) const { if (print_annotations) { std::ostringstream buf; commodity_t::print(buf, elide_quotes); write_annotations(buf); out << buf.str(); } else { commodity_t::print(out, elide_quotes); } } virtual void write_annotations(std::ostream& out, bool no_computed_annotations = false) const; }; inline annotated_commodity_t& as_annotated_commodity(commodity_t& commodity) { return downcast(commodity); } inline const annotated_commodity_t& as_annotated_commodity(const commodity_t& commodity) { return downcast(commodity); } } // namespace ledger #endif // _ANNOTATE_H ledger-3.1.1+dfsg1/src/balance.cc000066400000000000000000000236471266023441000164530ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "balance.h" #include "commodity.h" #include "annotate.h" #include "pool.h" #include "unistring.h" // for justify() namespace ledger { balance_t::balance_t(const double val) { amounts.insert (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); TRACE_CTOR(balance_t, "const double"); } balance_t::balance_t(const unsigned long val) { amounts.insert (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); TRACE_CTOR(balance_t, "const unsigned long"); } balance_t::balance_t(const long val) { amounts.insert (amounts_map::value_type(commodity_pool_t::current_pool->null_commodity, val)); TRACE_CTOR(balance_t, "const long"); } balance_t& balance_t::operator+=(const balance_t& bal) { foreach (const amounts_map::value_type& pair, bal.amounts) *this += pair.second; return *this; } balance_t& balance_t::operator+=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, _("Cannot add an uninitialized amount to a balance")); if (amt.is_realzero()) return *this; amounts_map::iterator i = amounts.find(&amt.commodity()); if (i != amounts.end()) i->second += amt; else amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); return *this; } balance_t& balance_t::operator-=(const balance_t& bal) { foreach (const amounts_map::value_type& pair, bal.amounts) *this -= pair.second; return *this; } balance_t& balance_t::operator-=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, _("Cannot subtract an uninitialized amount from a balance")); if (amt.is_realzero()) return *this; amounts_map::iterator i = amounts.find(&amt.commodity()); if (i != amounts.end()) { i->second -= amt; if (i->second.is_realzero()) amounts.erase(i); } else { amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negated())); } return *this; } balance_t& balance_t::operator*=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, _("Cannot multiply a balance by an uninitialized amount")); if (is_realzero()) { ; } else if (amt.is_realzero()) { *this = amt; } else if (! amt.commodity()) { // Multiplying by an amount with no commodity causes all the // component amounts to be increased by the same factor. foreach (amounts_map::value_type& pair, amounts) pair.second *= amt; } else if (amounts.size() == 1) { // Multiplying by a commoditized amount is only valid if the sole // commodity in the balance is of the same kind as the amount's // commodity. if (*amounts.begin()->first == amt.commodity()) amounts.begin()->second *= amt; else throw_(balance_error, _("Cannot multiply a balance with annotated commodities by a commoditized amount")); } else { assert(amounts.size() > 1); throw_(balance_error, _("Cannot multiply a multi-commodity balance by a commoditized amount")); } return *this; } balance_t& balance_t::operator/=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, _("Cannot divide a balance by an uninitialized amount")); if (is_realzero()) { ; } else if (amt.is_realzero()) { throw_(balance_error, _("Divide by zero")); } else if (! amt.commodity()) { // Dividing by an amount with no commodity causes all the // component amounts to be divided by the same factor. foreach (amounts_map::value_type& pair, amounts) pair.second /= amt; } else if (amounts.size() == 1) { // Dividing by a commoditized amount is only valid if the sole // commodity in the balance is of the same kind as the amount's // commodity. if (*amounts.begin()->first == amt.commodity()) amounts.begin()->second /= amt; else throw_(balance_error, _("Cannot divide a balance with annotated commodities by a commoditized amount")); } else { assert(amounts.size() > 1); throw_(balance_error, _("Cannot divide a multi-commodity balance by a commoditized amount")); } return *this; } optional balance_t::value(const datetime_t& moment, const commodity_t * in_terms_of) const { balance_t temp; bool resolved = false; foreach (const amounts_map::value_type& pair, amounts) { if (optional val = pair.second.value(moment, in_terms_of)) { temp += *val; resolved = true; } else { temp += pair.second; } } return resolved ? temp : optional(); } optional balance_t::commodity_amount(const optional& commodity) const { if (! commodity) { if (amounts.size() == 1) { return amounts.begin()->second; } else if (amounts.size() > 1) { // Try stripping annotations before giving an error. balance_t temp(strip_annotations(keep_details_t())); if (temp.amounts.size() == 1) return temp.commodity_amount(commodity); throw_(amount_error, _f("Requested amount of a balance with multiple commodities: %1%") % temp); } } else if (amounts.size() > 0) { amounts_map::const_iterator i = amounts.find(const_cast(&*commodity)); if (i != amounts.end()) return i->second; } return none; } balance_t balance_t::strip_annotations(const keep_details_t& what_to_keep) const { balance_t temp; foreach (const amounts_map::value_type& pair, amounts) temp += pair.second.strip_annotations(what_to_keep); return temp; } void balance_t::map_sorted_amounts(function fn) const { if (! amounts.empty()) { if (amounts.size() == 1) { const amount_t& amount((*amounts.begin()).second); if (amount) fn(amount); } else { typedef std::vector amounts_array; amounts_array sorted; foreach (const amounts_map::value_type& pair, amounts) if (pair.second) sorted.push_back(&pair.second); std::stable_sort(sorted.begin(), sorted.end(), commodity_t::compare_by_commodity()); foreach (const amount_t * amount, sorted) fn(*amount); } } } namespace { struct print_amount_from_balance { std::ostream& out; bool& first; int fwidth; int lwidth; uint_least8_t flags; explicit print_amount_from_balance(std::ostream& _out, bool& _first, int _fwidth, int _lwidth, uint_least8_t _flags) : out(_out), first(_first), fwidth(_fwidth), lwidth(_lwidth), flags(_flags) { TRACE_CTOR(print_amount_from_balance, "ostream&, int, int, uint_least8_t"); } print_amount_from_balance(const print_amount_from_balance& other) : out(other.out), first(other.first), fwidth(other.fwidth), lwidth(other.lwidth), flags(other.flags) { TRACE_CTOR(print_amount_from_balance, "copy"); } ~print_amount_from_balance() throw() { TRACE_DTOR(print_amount_from_balance); } void operator()(const amount_t& amount) { int width; if (! first) { out << std::endl; width = lwidth; } else { first = false; width = fwidth; } std::ostringstream buf; amount.print(buf, flags); justify(out, buf.str(), width, flags & AMOUNT_PRINT_RIGHT_JUSTIFY, flags & AMOUNT_PRINT_COLORIZE && amount.sign() < 0); } void close() { out.width(fwidth); if (flags & AMOUNT_PRINT_RIGHT_JUSTIFY) out << std::right; else out << std::left; out << 0; } }; } void balance_t::print(std::ostream& out, const int first_width, const int latter_width, const uint_least8_t flags) const { bool first = true; print_amount_from_balance amount_printer(out, first, first_width, latter_width == 1 ? first_width : latter_width, flags); map_sorted_amounts(amount_printer); if (first) amount_printer.close(); } void put_balance(property_tree::ptree& st, const balance_t& bal) { foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) put_amount(st.add("amount", ""), pair.second); } } // namespace ledger ledger-3.1.1+dfsg1/src/balance.h000066400000000000000000000452241266023441000163100ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup math */ /** * @file balance.h * @author John Wiegley * * @ingroup math * * @brief Basic type for adding multiple commodities together * * Unlike the amount_t class, which throws an exception if amounts of * differing commodities are added or subtracted, the balance_t class * is designed to allow this, tracking the amounts of each component * commodity separately. */ #ifndef _BALANCE_H #define _BALANCE_H #include "amount.h" namespace ledger { DECLARE_EXCEPTION(balance_error, std::runtime_error); /** * @class balance_t * * @brief A wrapper around amount_t allowing addition of multiple commodities. * * The balance_t class is appopriate for keeping a running balance * where amounts of multiple commodities may be involved. */ class balance_t : public equality_comparable > > > > > > > > > > > > > { public: typedef std::map amounts_map; amounts_map amounts; /** * Constructors. balance_t supports similar forms of construction * to amount_t. * * balance_t() creates an empty balance to which amounts or other * balances may be added or subtracted. * * balance_t(amount_t) constructs a balance whose starting value is * equal to the given amount. * * balance_t(double), balance_t(unsigned long) and balance_t(long) * will construct an amount from their arguments and then construct * a balance whose starting value is equal to that amount. This * initial balance will have no commodity. * * balance_t(string) and balance_t(const char *) both convert from a * string representation of an amount to a balance whose initial * value is that amount. This is the proper way to initialize a * balance like '$100.00'. */ balance_t() { TRACE_CTOR(balance_t, ""); } balance_t(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, _("Cannot initialize a balance from an uninitialized amount")); if (! amt.is_realzero()) amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); TRACE_CTOR(balance_t, "const amount_t&"); } balance_t(const double val); balance_t(const unsigned long val); balance_t(const long val); explicit balance_t(const string& val) { amount_t temp(val); amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); TRACE_CTOR(balance_t, "const string&"); } explicit balance_t(const char * val) { amount_t temp(val); amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); TRACE_CTOR(balance_t, "const char *"); } /** * Destructor. Destroys all of the accumulated amounts in the * balance. */ ~balance_t() { TRACE_DTOR(balance_t); } /** * Assignment and copy operators. An balance may be assigned or copied. */ balance_t(const balance_t& bal) : amounts(bal.amounts) { TRACE_CTOR(balance_t, "copy"); } balance_t& operator=(const balance_t& bal) { if (this != &bal) amounts = bal.amounts; return *this; } balance_t& operator=(const amount_t& amt) { if (amt.is_null()) throw_(balance_error, _("Cannot assign an uninitialized amount to a balance")); amounts.clear(); if (! amt.is_realzero()) amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); return *this; } balance_t& operator=(const string& str) { return *this = balance_t(str); } balance_t& operator=(const char * str) { return *this = balance_t(str); } /** * Comparison operators. Balances are fairly restrictive in terms * of how they may be compared. They may be compared for equality * or inequality, but this is all, since the concept of "less than" * or "greater than" makes no sense when amounts of multiple * commodities are involved. * * Balances may also be compared to amounts, in which case the sum * of the balance must equal the amount exactly. * * If a comparison between balances is desired, the balances must * first be rendered to value equivalent amounts using the `value' * method, to determine a market valuation at some specific moment * in time. */ bool operator==(const balance_t& bal) const { amounts_map::const_iterator i, j; for (i = amounts.begin(), j = bal.amounts.begin(); i != amounts.end() && j != bal.amounts.end(); i++, j++) { if (! (i->first == j->first && i->second == j->second)) return false; } return i == amounts.end() && j == bal.amounts.end(); } bool operator==(const amount_t& amt) const { if (amt.is_null()) throw_(balance_error, _("Cannot compare a balance to an uninitialized amount")); if (amt.is_realzero()) return amounts.empty(); else return amounts.size() == 1 && amounts.begin()->second == amt; } template bool operator==(const T& val) const { return *this == amount_t(val); } /** * Binary arithmetic operators. Balances support addition and * subtraction of other balances or amounts, but multiplication and * division are restricted to uncommoditized amounts only. */ balance_t& operator+=(const balance_t& bal); balance_t& operator+=(const amount_t& amt); balance_t& operator+=(const double val) { return *this += amount_t(val); } balance_t& operator+=(const unsigned long val) { return *this += amount_t(val); } balance_t& operator+=(const long val) { return *this += amount_t(val); } balance_t& operator-=(const balance_t& bal); balance_t& operator-=(const amount_t& amt); balance_t& operator-=(const double val) { return *this -= amount_t(val); } balance_t& operator-=(const unsigned long val) { return *this -= amount_t(val); } balance_t& operator-=(const long val) { return *this -= amount_t(val); } balance_t& operator*=(const amount_t& amt); balance_t& operator*=(const double val) { return *this *= amount_t(val); } balance_t& operator*=(const unsigned long val) { return *this *= amount_t(val); } balance_t& operator*=(const long val) { return *this *= amount_t(val); } balance_t& operator/=(const amount_t& amt); balance_t& operator/=(const double val) { return *this /= amount_t(val); } balance_t& operator/=(const unsigned long val) { return *this /= amount_t(val); } balance_t& operator/=(const long val) { return *this /= amount_t(val); } /** * Unary arithmetic operators. There are only a few unary methods * support on balance: * * negate(), also unary minus (- x), returns a balance all of whose * component amounts have been negated. In order words, it inverts * the sign of all member amounts. * * abs() returns a balance where no component amount is negative. * * reduce() reduces the values in a balance to their most basic * commodity forms, for amounts that utilize "scaling commodities". * For example, a balance of 1h and 1m after reduction will be * 3660s. * * unreduce(), if used with amounts that use "scaling commodities", * yields the most compact form greater than 1.0 for each component * amount. That is, a balance of 10m and 1799s will unreduce to * 39.98m. * * value(optional) returns the total historical value for * a balance -- the default moment returns a value based on the most * recently known price -- based on the price history of its * component commodities. See amount_t::value for an example. * * Further, for the sake of efficiency and avoiding temporary * objects, the following methods support "in-place" variants act on * the balance itself and return a reference to the result * (`*this'): * * in_place_negate() * in_place_reduce() * in_place_unreduce() */ balance_t negated() const { balance_t temp(*this); temp.in_place_negate(); return temp; } void in_place_negate() { foreach (amounts_map::value_type& pair, amounts) pair.second.in_place_negate(); } balance_t operator-() const { return negated(); } balance_t abs() const { balance_t temp; foreach (const amounts_map::value_type& pair, amounts) temp += pair.second.abs(); return temp; } balance_t rounded() const { balance_t temp(*this); temp.in_place_round(); return temp; } void in_place_round() { foreach (amounts_map::value_type& pair, amounts) 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(); return temp; } void in_place_truncate() { foreach (amounts_map::value_type& pair, amounts) pair.second.in_place_truncate(); } balance_t floored() const { balance_t temp(*this); temp.in_place_floor(); return temp; } void in_place_floor() { foreach (amounts_map::value_type& pair, amounts) 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(); return temp; } void in_place_unround() { foreach (amounts_map::value_type& pair, amounts) pair.second.in_place_unround(); } balance_t reduced() const { balance_t temp(*this); temp.in_place_reduce(); return temp; } void in_place_reduce() { // A temporary must be used here because reduction may cause // multiple component amounts to collapse to the same commodity. balance_t temp; foreach (const amounts_map::value_type& pair, amounts) temp += pair.second.reduced(); *this = temp; } balance_t unreduced() const { balance_t temp(*this); temp.in_place_unreduce(); return temp; } void in_place_unreduce() { // A temporary must be used here because unreduction may cause // multiple component amounts to collapse to the same commodity. balance_t temp; foreach (const amounts_map::value_type& pair, amounts) temp += pair.second.unreduced(); *this = temp; } optional value(const datetime_t& moment = datetime_t(), const commodity_t * in_terms_of = NULL) const; /** * Truth tests. An balance may be truth test in two ways: * * is_nonzero(), or operator bool, returns true if a balance's * display value is not zero. * * is_zero() returns true if an balance's display value is zero. * Thus, a balance containing $0.0001 is considered zero if the * current display precision for dollars is two decimal places. * * is_realzero() returns true if an balance's actual value is zero. * Thus, a balance containing $0.0001 is never considered realzero. * * is_empty() returns true if a balance has no amounts within it. * This can occur after a balance has been default initialized, or * if the exact amount it contains is subsequently subtracted from * it. */ operator bool() const { return is_nonzero(); } bool is_nonzero() const { if (is_empty()) return false; foreach (const amounts_map::value_type& pair, amounts) if (pair.second.is_nonzero()) return true; return false; } bool is_zero() const { if (is_empty()) return true; foreach (const amounts_map::value_type& pair, amounts) if (! pair.second.is_zero()) return false; return true; } bool is_realzero() const { if (is_empty()) return true; foreach (const amounts_map::value_type& pair, amounts) if (! pair.second.is_realzero()) return false; return true; } bool is_empty() const { return amounts.size() == 0; } bool single_amount() const { return amounts.size() == 1; } /** * Conversion methods. A balance can be converted to an amount, but * only if contains a single component amount. */ operator string() const { return to_string(); } string to_string() const { std::ostringstream buf; print(buf); return buf.str(); } amount_t to_amount() const { if (is_empty()) throw_(balance_error, _("Cannot convert an empty balance to an amount")); else if (amounts.size() == 1) return amounts.begin()->second; else throw_(balance_error, _("Cannot convert a balance with multiple commodities to an amount")); return amount_t(); } /** * Commodity-related methods. Balances support two * commodity-related methods: * * commodity_count() returns the number of different commodities * stored in the balance. * * commodity_amount(optional) returns an (optional) * amount for the given commodity within the balance; if no * commodity is specified, it returns the (optional) uncommoditized * component of the balance. If no matching element can be found, * boost::none is returned. */ std::size_t commodity_count() const { return amounts.size(); } optional commodity_amount(const optional& commodity = none) const; balance_t number() const { balance_t temp; foreach (const amounts_map::value_type& pair, amounts) temp += pair.second.number(); return temp; } /** * Annotated commodity methods. The amounts contained by a balance * may use annotated commodities. The `strip_annotations' method * will return a balance all of whose component amount have had * their commodity annotations likewise stripped. See * amount_t::strip_annotations for more details. */ balance_t strip_annotations(const keep_details_t& what_to_keep) const; /** * Iteration primitives. `map_sorted_amounts' allows one to visit * each amount in balance in the proper order for displaying to the * user. Mostly used by `print' and other routinse where the sort * order of the amounts' commodities is significant. */ void map_sorted_amounts(function fn) const; /** * Printing methods. A balance may be output to a stream using the * `print' method. There is also a global operator<< defined which * simply calls print for a balance on the given stream. There is * one form of the print method, which takes two required arguments * and one arguments with a default value: * * print(ostream, int first_width, int latter_width) prints a * balance to the given output stream, using each commodity's * default display characteristics. The first_width parameter * specifies the width that should be used for printing amounts * (since they are likely to vary in width). The latter_width, if * specified, gives the width to be used for each line after the * first. This is useful when printing in a column which falls at * the right-hand side of the screen. * * In addition to the width constraints, balances will also print * with commodities in alphabetized order, regardless of the * relative amounts of those commodities. There is no option to * change this behavior. */ void print(std::ostream& out, const int first_width = -1, const int latter_width = -1, const uint_least8_t flags = AMOUNT_PRINT_NO_FLAGS) const; /** * Debugging methods. There are two methods defined to help with * debugging: * * dump(ostream) dumps a balance to an output stream. There is * little different from print(), it simply surrounds the display * value with a marker, for example "BALANCE($1.00, DM 12.00)". * This code is used by other dumping code elsewhere in Ledger. * * valid() returns true if the amounts within the balance are valid. */ void dump(std::ostream& out) const { out << "BALANCE("; bool first = true; foreach (const amounts_map::value_type& pair, amounts) { if (first) first = false; else out << ", "; pair.second.print(out); } out << ")"; } bool valid() const { foreach (const amounts_map::value_type& pair, amounts) if (! pair.second.valid()) { DEBUG("ledger.validate", "balance_t: ! pair.second.valid()"); return false; } return true; } }; inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { bal.print(out, 12); return out; } void put_balance(property_tree::ptree& pt, const balance_t& bal); } // namespace ledger #endif // _BALANCE_H ledger-3.1.1+dfsg1/src/chain.cc000066400000000000000000000270521266023441000161420ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "chain.h" #include "predicate.h" #include "filters.h" #include "report.h" #include "session.h" namespace ledger { post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler, report_t& report) { post_handler_ptr handler(base_handler); // anonymize_posts removes all meaningful information from xact payee's and // account names, for the sake of creating useful bug reports. if (report.HANDLED(anon)) handler.reset(new anonymize_posts(handler)); // This filter_posts will only pass through posts matching the `predicate'. if (report.HANDLED(limit_)) { DEBUG("report.predicate", "Report predicate expression = " << report.HANDLER(limit_).str()); handler.reset(new filter_posts (handler, predicate_t(report.HANDLER(limit_).str(), report.what_to_keep()), report)); } // budget_posts takes a set of posts from a data file and uses them to // generate "budget posts" which balance against the reported posts. // // forecast_posts is a lot like budget_posts, except that it adds xacts // only for the future, and does not balance them against anything but the // future balance. if (report.budget_flags != BUDGET_NO_BUDGET) { budget_posts * budget_handler = new budget_posts(handler, report.terminus.date(), report.budget_flags); budget_handler->add_period_xacts(report.session.journal->period_xacts); handler.reset(budget_handler); // Apply this before the budget handler, so that only matching posts are // calculated toward the budget. The use of filter_posts above will // further clean the results so that no automated posts that don't match // the filter get reported. if (report.HANDLED(limit_)) handler.reset(new filter_posts (handler, predicate_t(report.HANDLER(limit_).str(), report.what_to_keep()), report)); } else if (report.HANDLED(forecast_while_)) { forecast_posts * forecast_handler = new forecast_posts(handler, predicate_t(report.HANDLER(forecast_while_).str(), report.what_to_keep()), report, (report.HANDLED(forecast_years_) ? lexical_cast (report.HANDLER(forecast_years_).value) : 5UL)); forecast_handler->add_period_xacts(report.session.journal->period_xacts); handler.reset(forecast_handler); // See above, under budget_posts. if (report.HANDLED(limit_)) handler.reset(new filter_posts (handler, predicate_t(report.HANDLER(limit_).str(), report.what_to_keep()), report)); } return handler; } post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, report_t& report, bool for_accounts_report) { post_handler_ptr handler(base_handler); predicate_t display_predicate; predicate_t only_predicate; display_filter_posts * display_filter = NULL; expr_t& expr(report.HANDLER(amount_).expr); expr.set_context(&report); report.HANDLER(total_).expr.set_context(&report); report.HANDLER(display_amount_).expr.set_context(&report); report.HANDLER(display_total_).expr.set_context(&report); if (! for_accounts_report) { // Make sure only forecast postings which match are allowed through if (report.HANDLED(forecast_while_)) { handler.reset(new filter_posts (handler, predicate_t(report.HANDLER(forecast_while_).str(), report.what_to_keep()), report)); } // truncate_xacts cuts off a certain number of _xacts_ from being // displayed. It does not affect calculation. if (report.HANDLED(head_) || report.HANDLED(tail_)) handler.reset (new truncate_xacts(handler, report.HANDLED(head_) ? lexical_cast(report.HANDLER(head_).value) : 0, report.HANDLED(tail_) ? lexical_cast(report.HANDLER(tail_).value) : 0)); // display_filter_posts adds virtual posts to the list to account // for changes in value of commodities, which otherwise would affect // the running total unpredictably. display_filter = new display_filter_posts(handler, report, report.HANDLED(revalued) && ! report.HANDLED(no_rounding)); handler.reset(display_filter); // filter_posts will only pass through posts matching the // `display_predicate'. if (report.HANDLED(display_)) { display_predicate = predicate_t(report.HANDLER(display_).str(), report.what_to_keep()); handler.reset(new filter_posts(handler, display_predicate, report)); } } // changed_value_posts adds virtual posts to the list to account for changes // in market value of commodities, which otherwise would affect the running // total unpredictably. if (report.HANDLED(revalued) && (! for_accounts_report || report.HANDLED(unrealized))) handler.reset(new changed_value_posts(handler, report, for_accounts_report, report.HANDLED(unrealized), display_filter)); // calc_posts computes the running total. When this appears will determine, // for example, whether filtered posts are included or excluded from the // running total. handler.reset(new calc_posts(handler, expr, (! for_accounts_report || (report.HANDLED(revalued) && report.HANDLED(unrealized))))); // filter_posts will only pass through posts matching the // `secondary_predicate'. if (report.HANDLED(only_)) { only_predicate = predicate_t(report.HANDLER(only_).str(), report.what_to_keep()); handler.reset(new filter_posts(handler, only_predicate, report)); } if (! for_accounts_report) { // sort_posts will sort all the posts it sees, based on the `sort_order' // value expression. if (report.HANDLED(sort_)) { if (report.HANDLED(sort_xacts_)) handler.reset(new sort_xacts(handler, report.HANDLER(sort_).str())); else handler.reset(new sort_posts(handler, report.HANDLER(sort_).str())); } // collapse_posts causes xacts with multiple posts to appear as xacts // with a subtotaled post for each commodity used. if (report.HANDLED(collapse)) handler.reset(new collapse_posts(handler, report, expr, display_predicate, only_predicate, report.HANDLED(collapse_if_zero))); // subtotal_posts combines all the posts it receives into one subtotal // xact, which has one post for each commodity in each account. // // period_posts is like subtotal_posts, but it subtotals according to time // periods rather than totalling everything. // // day_of_week_posts is like period_posts, except that it reports // all the posts that fall on each subsequent day of the week. if (report.HANDLED(equity)) handler.reset(new posts_as_equity(handler, report, expr)); else if (report.HANDLED(subtotal)) handler.reset(new subtotal_posts(handler, expr)); } if (report.HANDLED(dow)) handler.reset(new day_of_week_posts(handler, expr)); else if (report.HANDLED(by_payee)) handler.reset(new by_payee_posts(handler, expr)); // interval_posts groups posts together based on a time period, such as // weekly or monthly. if (report.HANDLED(period_)) handler.reset(new interval_posts(handler, expr, report.HANDLER(period_).str(), report.HANDLED(exact), report.HANDLED(empty))); if (report.HANDLED(date_)) handler.reset(new transfer_details(handler, transfer_details::SET_DATE, report.session.journal->master, report.HANDLER(date_).str(), report)); if (report.HANDLED(account_)) { handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT, report.session.journal->master, report.HANDLER(account_).str(), report)); } else if (report.HANDLED(pivot_)) { string pivot = report.HANDLER(pivot_).str(); pivot = string("\"") + pivot + ":\" + tag(\"" + pivot + "\")"; handler.reset(new transfer_details(handler, transfer_details::SET_ACCOUNT, report.session.journal->master, pivot, report)); } if (report.HANDLED(payee_)) handler.reset(new transfer_details(handler, transfer_details::SET_PAYEE, report.session.journal->master, report.HANDLER(payee_).str(), report)); // related_posts will pass along all posts related to the post received. If // the `related_all' handler is on, then all the xact's posts are passed; // meaning that if one post of an xact is to be printed, all the post for // that xact will be printed. if (report.HANDLED(related)) handler.reset(new related_posts(handler, report.HANDLED(related_all))); if (report.HANDLED(inject_)) handler.reset(new inject_posts(handler, report.HANDLED(inject_).str(), report.session.journal->master)); return handler; } } // namespace ledger ledger-3.1.1+dfsg1/src/chain.h000066400000000000000000000063651266023441000160100ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup report */ /** * @file chain.h * @author John Wiegley * * @ingroup report */ #ifndef _CHAIN_H #define _CHAIN_H #include "utils.h" namespace ledger { class post_t; class account_t; template class item_handler : public noncopyable { protected: shared_ptr handler; public: item_handler() { TRACE_CTOR(item_handler, ""); } item_handler(shared_ptr _handler) : handler(_handler) { TRACE_CTOR(item_handler, "shared_ptr"); } virtual ~item_handler() { TRACE_DTOR(item_handler); } virtual void title(const string& str) { if (handler) handler->title(str); } virtual void flush() { if (handler) handler->flush(); } virtual void operator()(T& item) { if (handler) { check_for_signal(); (*handler)(item); } } virtual void clear() { if (handler) handler->clear(); } }; typedef shared_ptr > post_handler_ptr; typedef shared_ptr > acct_handler_ptr; class report_t; post_handler_ptr chain_pre_post_handlers(post_handler_ptr base_handler, report_t& report); post_handler_ptr chain_post_handlers(post_handler_ptr base_handler, report_t& report, bool for_accounts_report = false); inline post_handler_ptr chain_handlers(post_handler_ptr handler, report_t& report, bool for_accounts_report = false) { handler = chain_post_handlers(handler, report, for_accounts_report); handler = chain_pre_post_handlers(handler, report); return handler; } } // namespace ledger #endif // _CHAIN_H ledger-3.1.1+dfsg1/src/commodity.cc000066400000000000000000000402761266023441000170670ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "amount.h" #include "commodity.h" #include "annotate.h" #include "pool.h" #include "scope.h" 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) { if (reflexive) { DEBUG("history.find", "Marking " << price.commodity().symbol() << " as a primary commodity"); price.commodity().add_flags(COMMODITY_PRIMARY); } else { DEBUG("history.find", "Marking " << symbol() << " as a primary commodity"); add_flags(COMMODITY_PRIMARY); } DEBUG("history.find", "Adding price: " << symbol() << " for " << price << " on " << date); pool().commodity_price_history.add_price(referent(), date, price); base->price_map.clear(); // a price was added, invalid the map } void commodity_t::remove_price(const datetime_t& date, commodity_t& commodity) { pool().commodity_price_history.remove_price(referent(), commodity, date); DEBUG("history.find", "Removing price: " << symbol() << " on " << date); base->price_map.clear(); // a price was added, invalid the map } void commodity_t::map_prices(function fn, const datetime_t& moment, const datetime_t& _oldest, bool bidirectionally) { datetime_t when; if (! moment.is_not_a_date_time()) when = moment; else if (epoch) when = *epoch; else when = CURRENT_TIME(); pool().commodity_price_history.map_prices(fn, referent(), when, _oldest, bidirectionally); } optional commodity_t::find_price_from_expr(expr_t& expr, const commodity_t * commodity, const datetime_t& moment) const { #if DEBUG_ON if (SHOW_DEBUG("commodity.price.find")) { ledger::_log_buffer << "valuation expr: "; expr.dump(ledger::_log_buffer); DEBUG("commodity.price.find", ""); } #endif value_t result(expr.calc(*scope_t::default_scope)); if (is_expr(result)) { value_t call_args; call_args.push_back(string_value(base_symbol())); call_args.push_back(moment); if (commodity) call_args.push_back(string_value(commodity->symbol())); result = as_expr(result)->call(call_args, *scope_t::default_scope); } return price_point_t(moment, result.to_amount()); } optional commodity_t::find_price(const commodity_t * commodity, const datetime_t& moment, const datetime_t& oldest) const { DEBUG("commodity.price.find", "commodity_t::find_price(" << symbol() << ")"); const commodity_t * target = NULL; if (commodity) target = commodity; else if (pool().default_commodity) target = &*pool().default_commodity; if (target && this == target) return none; base_t::memoized_price_entry entry(moment, oldest, commodity ? commodity : NULL); DEBUG("commodity.price.find", "looking for memoized args: " << (! moment.is_not_a_date_time() ? format_datetime(moment) : "NONE") << ", " << (! oldest.is_not_a_date_time() ? format_datetime(oldest) : "NONE") << ", " << (commodity ? commodity->symbol() : "NONE")); { base_t::memoized_price_map::iterator i = base->price_map.find(entry); if (i != base->price_map.end()) { DEBUG("commodity.price.find", "found! returning: " << ((*i).second ? (*i).second->price : amount_t(0L))); return (*i).second; } } datetime_t when; if (! moment.is_not_a_date_time()) when = moment; else if (epoch) when = *epoch; else when = CURRENT_TIME(); if (base->value_expr) return find_price_from_expr(*base->value_expr, commodity, when); optional point(target ? pool().commodity_price_history.find_price(referent(), *target, when, oldest) : pool().commodity_price_history.find_price(referent(), when, oldest)); // Record this price point in the memoization map if (base->price_map.size() > base_t::max_price_map_size) { DEBUG("history.find", "price map has grown too large, clearing it by half"); for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) base->price_map.erase(base->price_map.begin()); } DEBUG("history.find", "remembered: " << (point ? point->price : amount_t(0L))); base->price_map.insert(base_t::memoized_price_map::value_type(entry, point)); return point; } optional commodity_t::check_for_updated_price(const optional& point, const datetime_t& moment, const commodity_t* in_terms_of) { if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) { bool exceeds_leeway = true; if (point) { time_duration_t::sec_type seconds_diff; if (! moment.is_not_a_date_time()) { seconds_diff = (moment - point->when).total_seconds(); DEBUG("commodity.download", "moment = " << moment); DEBUG("commodity.download", "slip.moment = " << seconds_diff); } else { seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds(); DEBUG("commodity.download", "slip.now = " << seconds_diff); } DEBUG("commodity.download", "leeway = " << pool().quote_leeway); if (seconds_diff < pool().quote_leeway) exceeds_leeway = false; } if (exceeds_leeway) { DEBUG("commodity.download", "attempting to download a more current quote..."); if (optional quote = pool().get_commodity_quote(referent(), in_terms_of)) { if (! in_terms_of || (quote->price.has_commodity() && quote->price.commodity_ptr() == in_terms_of)) return quote; } } } return point; } commodity_t& commodity_t::nail_down(const expr_t& expr) { annotation_t new_details; new_details.value_expr = expr; new_details.add_flags(ANNOTATION_VALUE_EXPR_CALCULATED); return *pool().find_or_create(symbol(), new_details); } commodity_t::operator bool() const { return this != pool().null_commodity; } namespace { // Invalid commodity characters: // SPACE, TAB, NEWLINE, RETURN // 0-9 . , ; : ? ! - + * / ^ & | = // < > { } [ ] ( ) @ static int invalid_chars[256] = { /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ /* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, /* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, /* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; bool is_reserved_token(const char * buf) { switch (buf[0]) { case 'a': return std::strcmp(buf, "and") == 0; case 'd': return std::strcmp(buf, "div") == 0; case 'e': return std::strcmp(buf, "else") == 0; case 'f': return std::strcmp(buf, "false") == 0; case 'i': return std::strcmp(buf, "if") == 0; case 'o': return std::strcmp(buf, "or") == 0; case 'n': return std::strcmp(buf, "not") == 0; case 't': return std::strcmp(buf, "true") == 0; } return false; } } bool commodity_t::symbol_needs_quotes(const string& symbol) { foreach (char ch, symbol) if (invalid_chars[static_cast(ch)]) return true; return false; } void commodity_t::parse_symbol(std::istream& in, string& symbol) { istream_pos_type pos = in.tellg(); char buf[256]; char c = peek_next_nonws(in); if (c == '"') { in.get(c); READ_INTO(in, buf, 255, c, c != '"'); if (c == '"') in.get(c); else throw_(amount_error, _("Quoted commodity symbol lacks closing quote")); } else { char * _p = buf; while (_p - buf < 255 && in.good() && ! in.eof() && c != '\n') { std::size_t bytes = 0; std::ptrdiff_t size = _p - buf; unsigned char d = static_cast(c); // Check for the start of a UTF-8 multi-byte encoded string if (d >= 192 && d <= 223 && size < 254) bytes = 2; else if (d >= 224 && d <= 239 && size < 253) bytes = 3; else if (d >= 240 && d <= 247 && size < 252) bytes = 4; else if (d >= 248 && d <= 251 && size < 251) bytes = 5; else if (d >= 252 && d <= 253 && size < 250) bytes = 6; else if (d >= 254) // UTF-8 encoding error break; if (bytes > 0) { // we're looking at a UTF-8 encoding for (std::size_t i = 0; i < bytes; i++) { in.get(c); if (in.bad() || in.eof()) throw_(amount_error, _("Invalid UTF-8 encoding for commodity name")); *_p++ = c; } } else if (invalid_chars[static_cast(c)]) { break; } else { in.get(c); if (in.eof()) break; if (c == '\\') { in.get(c); if (in.eof()) throw_(amount_error, _("Backslash at end of commodity name")); } *_p++ = c; } c = static_cast(in.peek()); } *_p = '\0'; if (is_reserved_token(buf)) buf[0] = '\0'; } symbol = buf; if (symbol.length() == 0) { in.clear(); in.seekg(pos, std::ios::beg); } } void commodity_t::parse_symbol(char *& p, string& symbol) { if (*p == '"') { char * q = std::strchr(p + 1, '"'); if (! q) throw_(amount_error, _("Quoted commodity symbol lacks closing quote")); symbol = string(p + 1, 0, static_cast(q - p - 1)); p = q + 2; } else { char * q = next_element(p); symbol = p; if (q) p = q; else p += symbol.length(); } if (symbol.empty()) throw_(amount_error, _("Failed to parse commodity")); } void commodity_t::print(std::ostream& out, bool elide_quotes, bool) const { string sym = symbol(); if (elide_quotes && has_flags(COMMODITY_STYLE_SEPARATED) && ! sym.empty() && sym[0] == '"' && ! std::strchr(sym.c_str(), ' ')) { string subsym(sym, 1, sym.length() - 2); if (! all(subsym, is_digit())) out << subsym; else out << sym; } else out << sym; } bool commodity_t::valid() const { if (symbol().empty() && this != pool().null_commodity) { DEBUG("ledger.validate", "commodity_t: symbol().empty() && this != null_commodity"); return false; } if (annotated && ! base) { DEBUG("ledger.validate", "commodity_t: annotated && ! base"); return false; } if (precision() > 16) { DEBUG("ledger.validate", "commodity_t: precision() > 16"); return false; } return true; } bool commodity_t::compare_by_commodity::operator()(const amount_t * left, const amount_t * right) const { commodity_t& leftcomm(left->commodity()); commodity_t& rightcomm(right->commodity()); DEBUG("commodity.compare", " left symbol (" << leftcomm << ")"); DEBUG("commodity.compare", "right symbol (" << rightcomm << ")"); int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol()); if (cmp != 0) return cmp < 0; if (! leftcomm.has_annotation()) { return rightcomm.has_annotation(); } else if (! rightcomm.has_annotation()) { return ! leftcomm.has_annotation(); } else { annotated_commodity_t& aleftcomm(static_cast(leftcomm)); annotated_commodity_t& arightcomm(static_cast(rightcomm)); if (! aleftcomm.details.price && arightcomm.details.price) return true; if (aleftcomm.details.price && ! arightcomm.details.price) return false; if (aleftcomm.details.price && arightcomm.details.price) { amount_t leftprice(*aleftcomm.details.price); amount_t rightprice(*arightcomm.details.price); if (leftprice.commodity() == rightprice.commodity()) { return (leftprice - rightprice).sign() < 0; } else { // Since we have two different amounts, there's really no way // to establish a true sorting order; we'll just do it based // on the numerical values. leftprice.clear_commodity(); rightprice.clear_commodity(); return (leftprice - rightprice).sign() < 0; } } if (! aleftcomm.details.date && arightcomm.details.date) return true; if (aleftcomm.details.date && ! arightcomm.details.date) return false; if (aleftcomm.details.date && arightcomm.details.date) { gregorian::date_duration diff = *aleftcomm.details.date - *arightcomm.details.date; return diff.is_negative(); } if (! aleftcomm.details.tag && arightcomm.details.tag) return true; if (aleftcomm.details.tag && ! arightcomm.details.tag) return false; if (aleftcomm.details.tag && arightcomm.details.tag) return *aleftcomm.details.tag < *arightcomm.details.tag; if (! aleftcomm.details.value_expr && arightcomm.details.value_expr) return true; if (aleftcomm.details.value_expr && ! arightcomm.details.value_expr) return false; if (aleftcomm.details.value_expr && arightcomm.details.value_expr) return (aleftcomm.details.value_expr->text() < arightcomm.details.value_expr->text()); assert(false); return true; } } void put_commodity(property_tree::ptree& st, const commodity_t& comm, bool commodity_details) { std::string flags; if (! (comm.has_flags(COMMODITY_STYLE_SUFFIXED))) flags += 'P'; if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) flags += 'S'; if (comm.has_flags(COMMODITY_STYLE_THOUSANDS)) flags += 'T'; if (comm.has_flags(COMMODITY_STYLE_DECIMAL_COMMA)) flags += 'D'; st.put(".flags", flags); st.put("symbol", comm.symbol()); if (commodity_details && comm.has_annotation()) put_annotation(st.put("annotation", ""), as_annotated_commodity(comm).details); } } // namespace ledger ledger-3.1.1+dfsg1/src/commodity.h000066400000000000000000000215421266023441000167240ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup math */ /** * @file commodity.h * @author John Wiegley * * @ingroup math * * @brief Types for handling commodities * * This file contains one of the most basic types in Ledger: * commodity_t, and its annotated cousin, annotated_commodity_t. */ #ifndef _COMMODITY_H #define _COMMODITY_H #include "expr.h" namespace ledger { struct keep_details_t; class commodity_pool_t; DECLARE_EXCEPTION(commodity_error, std::runtime_error); struct price_point_t { datetime_t when; amount_t price; price_point_t() {} price_point_t(datetime_t _when, amount_t _price) : when(_when), price(_price) {} bool operator==(const price_point_t& other) const { return when == other.when && price == other.price; } }; class commodity_t : public delegates_flags, public equality_comparable1 { protected: friend class commodity_pool_t; friend class annotated_commodity_t; class base_t : public noncopyable, public supports_flags { public: #define COMMODITY_STYLE_DEFAULTS 0x000 #define COMMODITY_STYLE_SUFFIXED 0x001 #define COMMODITY_STYLE_SEPARATED 0x002 #define COMMODITY_STYLE_DECIMAL_COMMA 0x004 #define COMMODITY_STYLE_THOUSANDS 0x008 #define COMMODITY_NOMARKET 0x010 #define COMMODITY_BUILTIN 0x020 #define COMMODITY_WALKED 0x040 #define COMMODITY_KNOWN 0x080 #define COMMODITY_PRIMARY 0x100 #define COMMODITY_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 graph_index; amount_t::precision_t precision; optional name; optional note; optional smaller; optional larger; optional value_expr; typedef tuple memoized_price_entry; typedef std::map > memoized_price_map; static const std::size_t max_price_map_size = 8; mutable memoized_price_map price_map; public: explicit base_t(const string& _symbol) : supports_flags (commodity_t::decimal_comma_by_default ? static_cast(COMMODITY_STYLE_DECIMAL_COMMA) : static_cast(COMMODITY_STYLE_DEFAULTS)), symbol(_symbol), precision(0) { TRACE_CTOR(commodity_t::base_t, "const string&"); } virtual ~base_t() { TRACE_DTOR(commodity_t::base_t); } }; shared_ptr base; commodity_pool_t * parent_; optional qualified_symbol; bool annotated; explicit commodity_t(commodity_pool_t * _parent, const shared_ptr& _base) : delegates_flags(*_base.get()), base(_base), parent_(_parent), annotated(false) { TRACE_CTOR(commodity_t, "commodity_pool_t *, shared_ptr"); } public: static bool decimal_comma_by_default; static bool time_colon_by_default; virtual ~commodity_t() { TRACE_DTOR(commodity_t); } operator bool() const; virtual bool operator==(const commodity_t& comm) const { if (comm.annotated) return comm == *this; return base.get() == comm.base.get(); } bool operator==(const string& name) const { return base_symbol() == name; } static bool symbol_needs_quotes(const string& symbol); virtual commodity_t& referent() { return *this; } virtual const commodity_t& referent() const { return *this; } bool has_annotation() const { return annotated; } virtual commodity_t& strip_annotations(const keep_details_t&) { return *this; } virtual void write_annotations(std::ostream&, bool) const {} commodity_pool_t& pool() const { return *parent_; } string base_symbol() const { return base->symbol; } string symbol() const { return qualified_symbol ? *qualified_symbol : base_symbol(); } optional graph_index() const {; return base->graph_index; } void set_graph_index(const optional& arg = none) { base->graph_index = arg; } optional name() const { return base->name; } void set_name(const optional& arg = none) { base->name = arg; } optional note() const { return base->note; } void set_note(const optional& arg = none) { base->note = arg; } amount_t::precision_t precision() const { return base->precision; } void set_precision(amount_t::precision_t arg) { base->precision = arg; } optional smaller() const { return base->smaller; } void set_smaller(const optional& arg = none) { base->smaller = arg; } optional larger() const { return base->larger; } void set_larger(const optional& arg = none) { base->larger = arg; } virtual optional value_expr() const { return base->value_expr; } void set_value_expr(const optional& expr = none) { base->value_expr = expr; } void add_price(const datetime_t& date, const amount_t& price, const bool reflexive = true); void remove_price(const datetime_t& date, commodity_t& commodity); void map_prices(function fn, const datetime_t& moment = datetime_t(), const datetime_t& _oldest = datetime_t(), bool bidirectionally = false); optional find_price_from_expr(expr_t& expr, const commodity_t * commodity, const datetime_t& moment) const; optional virtual find_price(const commodity_t * commodity = NULL, const datetime_t& moment = datetime_t(), const datetime_t& oldest = datetime_t()) const; optional check_for_updated_price(const optional& point, const datetime_t& moment, const commodity_t * in_terms_of); commodity_t& nail_down(const expr_t& expr); // Methods related to parsing, reading, writing, etc., the commodity // itself. static void parse_symbol(std::istream& in, string& symbol); static void parse_symbol(char *& p, string& symbol); static string parse_symbol(std::istream& in) { string temp; parse_symbol(in, temp); return temp; } virtual void print(std::ostream& out, bool elide_quotes = false, bool print_annotations = false) const; bool valid() const; struct compare_by_commodity { bool operator()(const amount_t * left, const amount_t * right) const; }; }; inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { comm.print(out, false, true); return out; } 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) const { return (lhs->symbol().compare(rhs->symbol()) < 0); } }; } // namespace ledger #endif // _COMMODITY_H ledger-3.1.1+dfsg1/src/compare.cc000066400000000000000000000077341266023441000165130ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "compare.h" #include "op.h" #include "post.h" #include "account.h" namespace ledger { void push_sort_value(std::list& sort_values, expr_t::ptr_op_t node, scope_t& scope) { if (node->kind == expr_t::op_t::O_CONS) { while (node && node->kind == expr_t::op_t::O_CONS) { push_sort_value(sort_values, node->left(), scope); node = node->has_right() ? node->right() : NULL; } } else { bool inverted = false; if (node->kind == expr_t::op_t::O_NEG) { inverted = true; node = node->left(); } sort_values.push_back(sort_value_t()); sort_values.back().inverted = inverted; sort_values.back().value = expr_t(node).calc(scope).simplified(); if (sort_values.back().value.is_null()) throw_(calc_error, _("Could not determine sorting value based an expression")); } } template <> bool compare_items::operator()(post_t * left, post_t * right) { assert(left); assert(right); post_t::xdata_t& lxdata(left->xdata()); if (! lxdata.has_flags(POST_EXT_SORT_CALC)) { bind_scope_t bound_scope(*sort_order.get_context(), *left); find_sort_values(lxdata.sort_values, bound_scope); lxdata.add_flags(POST_EXT_SORT_CALC); } post_t::xdata_t& rxdata(right->xdata()); if (! rxdata.has_flags(POST_EXT_SORT_CALC)) { bind_scope_t bound_scope(*sort_order.get_context(), *right); find_sort_values(rxdata.sort_values, bound_scope); rxdata.add_flags(POST_EXT_SORT_CALC); } return sort_value_is_less_than(lxdata.sort_values, rxdata.sort_values); } template <> bool compare_items::operator()(account_t * left, account_t * right) { assert(left); assert(right); account_t::xdata_t& lxdata(left->xdata()); if (! lxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { bind_scope_t bound_scope(*sort_order.get_context(), *left); find_sort_values(lxdata.sort_values, bound_scope); lxdata.add_flags(ACCOUNT_EXT_SORT_CALC); } account_t::xdata_t& rxdata(right->xdata()); if (! rxdata.has_flags(ACCOUNT_EXT_SORT_CALC)) { bind_scope_t bound_scope(*sort_order.get_context(), *right); find_sort_values(rxdata.sort_values, bound_scope); rxdata.add_flags(ACCOUNT_EXT_SORT_CALC); } DEBUG("value.sort", "Comparing accounts " << left->fullname() << " <> " << right->fullname()); return sort_value_is_less_than(lxdata.sort_values, rxdata.sort_values); } } // namespace ledger ledger-3.1.1+dfsg1/src/compare.h000066400000000000000000000057621266023441000163540ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup data */ /** * @file compare.h * @author John Wiegley * * @ingroup data */ #ifndef _COMPARE_H #define _COMPARE_H #include "expr.h" namespace ledger { class post_t; class account_t; void push_sort_value(std::list& sort_values, expr_t::ptr_op_t node, scope_t& scope); template class compare_items { expr_t sort_order; compare_items(); public: compare_items(const compare_items& other) : sort_order(other.sort_order) { TRACE_CTOR(compare_items, "copy"); } compare_items(const expr_t& _sort_order) : sort_order(_sort_order) { TRACE_CTOR(compare_items, "const value_expr&"); } ~compare_items() throw() { TRACE_DTOR(compare_items); } void find_sort_values(std::list& sort_values, scope_t& scope) { push_sort_value(sort_values, sort_order.get_op(), scope); } bool operator()(T * left, T * right); }; sort_value_t calc_sort_value(const expr_t::ptr_op_t op); template bool compare_items::operator()(T * left, T * right) { assert(left); assert(right); return sort_value_is_less_than(find_sort_values(left), find_sort_values(right)); } template <> bool compare_items::operator()(post_t * left, post_t * right); template <> bool compare_items::operator()(account_t * left, account_t * right); } // namespace ledger #endif // _COMPARE_H ledger-3.1.1+dfsg1/src/context.h000066400000000000000000000116301266023441000164010ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup data */ /** * @file context.h * @author John Wiegley * * @ingroup data */ #ifndef _CONTEXT_H #define _CONTEXT_H #include "utils.h" #include "times.h" namespace ledger { class journal_t; class account_t; class scope_t; class parse_context_t { public: static const std::size_t MAX_LINE = 4096; shared_ptr stream; path pathname; path current_directory; journal_t * journal; account_t * master; scope_t * scope; char linebuf[MAX_LINE + 1]; istream_pos_type line_beg_pos; istream_pos_type curr_pos; std::size_t linenum; std::size_t errors; std::size_t count; std::size_t sequence; explicit parse_context_t(const path& cwd) : current_directory(cwd), master(NULL), scope(NULL), linenum(0), errors(0), count(0), sequence(1) {} explicit parse_context_t(shared_ptr _stream, const path& cwd) : stream(_stream), current_directory(cwd), master(NULL), scope(NULL), linenum(0), errors(0), count(0), sequence(1) {} parse_context_t(const parse_context_t& context) : stream(context.stream), pathname(context.pathname), current_directory(context.current_directory), journal(context.journal), master(context.master), scope(context.scope), line_beg_pos(context.line_beg_pos), curr_pos(context.curr_pos), linenum(context.linenum), errors(context.errors), count(context.count), sequence(context.sequence) { std::memcpy(linebuf, context.linebuf, MAX_LINE); } string location() const { return file_context(pathname, linenum); } void warning(const string& what) const { warning_func(location() + " " + what); } void warning(const boost::format& what) const { warning_func(location() + " " + string(what.str())); } }; inline parse_context_t open_for_reading(const path& pathname, const path& cwd) { path filename = resolve_path(pathname); #if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 filename = filesystem::absolute(filename, cwd); #else 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 stream(new ifstream(filename)); parse_context_t context(stream, parent); context.pathname = filename; return context; } class parse_context_stack_t { std::list parsing_context; public: void push() { parsing_context.push_front(parse_context_t(filesystem::current_path())); } void push(shared_ptr stream, const path& cwd = filesystem::current_path()) { parsing_context.push_front(parse_context_t(stream, cwd)); } void push(const path& pathname, const path& cwd = filesystem::current_path()) { parsing_context.push_front(open_for_reading(pathname, cwd)); } void push(const parse_context_t& context) { parsing_context.push_front(context); } void pop() { assert(! parsing_context.empty()); parsing_context.pop_front(); } parse_context_t& get_current() { assert(! parsing_context.empty()); return parsing_context.front(); } }; } // namespace ledger #endif // _CONTEXT_H ledger-3.1.1+dfsg1/src/convert.cc000066400000000000000000000114251266023441000165350ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "convert.h" #include "csv.h" #include "scope.h" #include "iterators.h" #include "report.h" #include "xact.h" #include "print.h" #include "lookup.h" namespace ledger { value_t convert_command(call_scope_t& args) { report_t& report(args.context()); journal_t& journal(*report.session.journal.get()); string bucket_name; if (report.HANDLED(account_)) bucket_name = report.HANDLER(account_).str(); else bucket_name = _("Equity:Unknown"); account_t * bucket = journal.master->find_account(bucket_name); account_t * unknown = journal.master->find_account(_("Expenses:Unknown")); // Create a flat list xacts_list current_xacts(journal.xacts_begin(), journal.xacts_end()); // Read in the series of transactions from the CSV file print_xacts formatter(report); path csv_file_path(args.get(0)); report.session.parsing_context.push(csv_file_path); parse_context_t& context(report.session.parsing_context.get_current()); context.journal = &journal; context.master = bucket; csv_reader reader(context); try { while (xact_t * xact = reader.read_xact(report.HANDLED(rich_data))) { if (report.HANDLED(invert)) { foreach (post_t * post, xact->posts) post->amount.in_place_negate(); } string ref = (xact->has_tag(_("UUID")) ? xact->get_tag(_("UUID"))->to_string() : sha1sum(reader.get_last_line())); checksum_map_t::const_iterator entry = journal.checksum_map.find(ref); if (entry != journal.checksum_map.end()) { INFO(file_context(reader.get_pathname(), reader.get_linenum()) << " " << "Ignoring known UUID " << ref); checked_delete(xact); // ignore it continue; } if (report.HANDLED(rich_data) && ! xact->has_tag(_("UUID"))) xact->set_tag(_("UUID"), string_value(ref)); if (xact->posts.front()->account == NULL) { if (account_t * acct = (report.HANDLED(auto_match) ? lookup_probable_account(xact->payee, current_xacts.rbegin(), current_xacts.rend(), bucket).second : NULL)) xact->posts.front()->account = acct; else xact->posts.front()->account = unknown; } if (! journal.add_xact(xact)) { checked_delete(xact); throw_(std::runtime_error, _("Failed to finalize derived transaction (check commodities)")); } else { xact_posts_iterator xact_iter(*xact); while (post_t * post = *xact_iter++) formatter(*post); } } formatter.flush(); } catch (const std::exception&) { add_error_context(_f("While parsing file %1%") % file_context(reader.get_pathname(), reader.get_linenum())); add_error_context(_("While parsing CSV line:")); add_error_context(line_context(reader.get_last_line())); throw; } // If not, transform the payee according to regexps // Set the account to a default vaule, then transform the account according // to the payee // Print out the final form of the transaction return true; } } // namespace ledger ledger-3.1.1+dfsg1/src/convert.h000066400000000000000000000035201266023441000163740ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup data */ /** * @file convert.h * @author John Wiegley * * @ingroup data */ #ifndef _CONVERT_H #define _CONVERT_H #include "value.h" namespace ledger { class call_scope_t; value_t convert_command(call_scope_t& scope); } // namespace ledger #endif // _CONVERT_H ledger-3.1.1+dfsg1/src/csv.cc000066400000000000000000000172341266023441000156540ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "csv.h" #include "xact.h" #include "post.h" #include "account.h" #include "journal.h" #include "pool.h" namespace ledger { string csv_reader::read_field(std::istream& in) { string field; char c; if (in.peek() == '"' || in.peek() == '|') { in.get(c); char x; while (in.good() && ! in.eof()) { in.get(x); if (x == '\\') { in.get(x); } else if (x == '"' && in.peek() == '"') { in.get(x); } else if (x == c) { if (x == '|') in.unget(); else if (in.peek() == ',') in.get(c); break; } if (x != '\0') field += x; } } else { while (in.good() && ! in.eof()) { in.get(c); if (in.good()) { if (c == ',') break; if (c != '\0') field += c; } } } trim(field); return field; } char * csv_reader::next_line(std::istream& in) { while (in.good() && ! in.eof() && in.peek() == '#') in.getline(context.linebuf, parse_context_t::MAX_LINE); if (! in.good() || in.eof() || in.peek() == -1) return NULL; in.getline(context.linebuf, parse_context_t::MAX_LINE); return context.linebuf; } void csv_reader::read_index(std::istream& in) { char * line = next_line(in); if (! line) return; std::istringstream instr(line); while (instr.good() && ! instr.eof()) { string field = read_field(instr); names.push_back(field); if (date_mask.match(field)) index.push_back(FIELD_DATE); else if (date_aux_mask.match(field)) index.push_back(FIELD_DATE_AUX); else if (code_mask.match(field)) index.push_back(FIELD_CODE); else if (payee_mask.match(field)) index.push_back(FIELD_PAYEE); else if (amount_mask.match(field)) index.push_back(FIELD_AMOUNT); else if (cost_mask.match(field)) index.push_back(FIELD_COST); else if (total_mask.match(field)) index.push_back(FIELD_TOTAL); else if (note_mask.match(field)) index.push_back(FIELD_NOTE); else index.push_back(FIELD_UNKNOWN); DEBUG("csv.parse", "Header field: " << field); } } xact_t * csv_reader::read_xact(bool rich_data) { char * line = next_line(*context.stream.get()); if (! line || index.empty()) return NULL; context.linenum++; std::istringstream instr(line); unique_ptr xact(new xact_t); unique_ptr post(new post_t); xact->set_state(item_t::CLEARED); xact->pos = position_t(); xact->pos->pathname = context.pathname; xact->pos->beg_pos = context.stream->tellg(); xact->pos->beg_line = context.linenum; xact->pos->sequence = context.sequence++; post->xact = xact.get(); post->pos = position_t(); post->pos->pathname = context.pathname; post->pos->beg_pos = context.stream->tellg(); post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; post->set_state(item_t::CLEARED); post->account = NULL; std::vector::size_type n = 0; amount_t amt; string total; string field; while (instr.good() && ! instr.eof() && n < index.size()) { field = read_field(instr); switch (index[n]) { case FIELD_DATE: xact->_date = parse_date(field); break; case FIELD_DATE_AUX: if (! field.empty()) xact->_date_aux = parse_date(field); break; case FIELD_CODE: if (! field.empty()) xact->code = field; break; case FIELD_PAYEE: { bool found = false; foreach (payee_alias_mapping_t& value, context.journal->payee_alias_mappings) { DEBUG("csv.mappings", "Looking for payee mapping: " << value.first); if (value.first.match(field)) { xact->payee = value.second; found = true; break; } } if (! found) xact->payee = field; break; } case FIELD_AMOUNT: { std::istringstream amount_str(field); amt.parse(amount_str, PARSE_NO_REDUCE); if (! amt.has_commodity() && commodity_pool_t::current_pool->default_commodity) amt.set_commodity(*commodity_pool_t::current_pool->default_commodity); post->amount = amt; break; } case FIELD_COST: { std::istringstream amount_str(field); amt.parse(amount_str, PARSE_NO_REDUCE); if (! amt.has_commodity() && commodity_pool_t::current_pool->default_commodity) amt.set_commodity (*commodity_pool_t::current_pool->default_commodity); post->cost = amt; break; } case FIELD_TOTAL: total = field; break; case FIELD_NOTE: if (! field.empty()) xact->note = field; break; case FIELD_UNKNOWN: if (! names[n].empty() && ! field.empty()) xact->set_tag(names[n], string_value(field)); break; } n++; } if (rich_data) { xact->set_tag(_("Imported"), string_value(format_date(CURRENT_DATE(), FMT_WRITTEN))); xact->set_tag(_("CSV"), string_value(line)); } // Translate the account name, if we have enough information to do so foreach (account_mapping_t& value, context.journal->payees_for_unknown_accounts) { if (value.first.match(xact->payee)) { post->account = value.second; break; } } xact->add_post(post.release()); // Create the "balancing post", which refers to the account for this data post.reset(new post_t); post->xact = xact.get(); post->pos = position_t(); post->pos->pathname = context.pathname; post->pos->beg_pos = context.stream->tellg(); post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; post->set_state(item_t::CLEARED); post->account = context.master; if (! amt.is_null()) post->amount = - amt; if (! total.empty()) { std::istringstream assigned_amount_str(total); amt.parse(assigned_amount_str, PARSE_NO_REDUCE); if (! amt.has_commodity() && commodity_pool_t::current_pool->default_commodity) amt.set_commodity(*commodity_pool_t::current_pool->default_commodity); post->assigned_amount = amt; } xact->add_post(post.release()); return xact.release(); } } // namespace ledger ledger-3.1.1+dfsg1/src/csv.h000066400000000000000000000061051266023441000155110ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup data */ /** * @file csv.h * @author John Wiegley * * @ingroup data */ #ifndef _CSV_H #define _CSV_H #include "value.h" #include "context.h" namespace ledger { class xact_t; class journal_t; class account_t; class csv_reader { parse_context_t context; enum headers_t { FIELD_DATE = 0, FIELD_DATE_AUX, FIELD_CODE, FIELD_PAYEE, FIELD_AMOUNT, FIELD_COST, FIELD_TOTAL, FIELD_NOTE, FIELD_UNKNOWN }; mask_t date_mask; mask_t date_aux_mask; mask_t code_mask; mask_t payee_mask; mask_t amount_mask; mask_t cost_mask; mask_t total_mask; mask_t note_mask; std::vector index; std::vector names; public: csv_reader(parse_context_t& _context) : context(_context), date_mask("date"), date_aux_mask("posted( ?date)?"), code_mask("code"), payee_mask("(payee|desc(ription)?|title)"), amount_mask("amount"), cost_mask("cost"), total_mask("total"), note_mask("note") { read_index(*context.stream.get()); TRACE_CTOR(csv_reader, "parse_context_t&"); } ~csv_reader() { TRACE_DTOR(csv_reader); } void read_index(std::istream& in); string read_field(std::istream& in); char * next_line(std::istream& in); xact_t * read_xact(bool rich_data); const char * get_last_line() const { return context.linebuf; } path get_pathname() const { return context.pathname; } std::size_t get_linenum() const { return context.linenum; } }; } // namespace ledger #endif // _CSV_H ledger-3.1.1+dfsg1/src/draft.cc000066400000000000000000000417011266023441000161550ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "draft.h" #include "xact.h" #include "post.h" #include "account.h" #include "journal.h" #include "session.h" #include "report.h" #include "lookup.h" #include "print.h" namespace ledger { void draft_t::xact_template_t::dump(std::ostream& out) const { if (date) out << _("Date: ") << *date << std::endl; else out << _("Date: ") << std::endl; if (code) out << _("Code: ") << *code << std::endl; if (note) out << _("Note: ") << *note << std::endl; if (payee_mask.empty()) out << _("Payee mask: INVALID (template expression will cause an error)") << std::endl; else out << _("Payee mask: ") << payee_mask << std::endl; if (posts.empty()) { out << std::endl << _("") << std::endl; } else { foreach (const post_template_t& post, posts) { out << std::endl << _f("[Posting \"%1\"]") % (post.from ? _("from") : _("to")) << std::endl; if (post.account_mask) out << _(" Account mask: ") << *post.account_mask << std::endl; else if (post.from) out << _(" Account mask: ") << std::endl; else out << _(" Account mask: ") << std::endl; if (post.amount) out << _(" Amount: ") << *post.amount << std::endl; if (post.cost) out << _(" Cost: ") << *post.cost_operator << " " << *post.cost << std::endl; } } } void draft_t::parse_args(const value_t& args) { regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?")); smatch what; bool check_for_date = true; tmpl = xact_template_t(); optional weekday; xact_template_t::post_template_t * post = NULL; value_t::sequence_t::const_iterator begin = args.begin(); value_t::sequence_t::const_iterator end = args.end(); for (; begin != end; begin++) { if (check_for_date && regex_match((*begin).to_string(), what, date_mask)) { tmpl->date = parse_date(what[0]); check_for_date = false; } else if (check_for_date && bool(weekday = string_to_day_of_week(what[0]))) { #if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif short dow = static_cast(*weekday); #if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 #pragma GCC diagnostic pop #endif date_t date = CURRENT_DATE() - date_duration(1); while (date.day_of_week() != dow) date -= date_duration(1); tmpl->date = date; check_for_date = false; } else { string arg = (*begin).to_string(); if (arg == "at") { if (begin == end) throw std::runtime_error(_("Invalid xact command arguments")); tmpl->payee_mask = (*++begin).to_string(); } else if (arg == "to" || arg == "from") { if (! post || post->account_mask) { tmpl->posts.push_back(xact_template_t::post_template_t()); post = &tmpl->posts.back(); } if (begin == end) throw std::runtime_error(_("Invalid xact command arguments")); post->account_mask = mask_t((*++begin).to_string()); post->from = arg == "from"; } else if (arg == "on") { if (begin == end) throw std::runtime_error(_("Invalid xact command arguments")); tmpl->date = parse_date((*++begin).to_string()); check_for_date = false; } else if (arg == "code") { if (begin == end) throw std::runtime_error(_("Invalid xact command arguments")); tmpl->code = (*++begin).to_string(); } else if (arg == "note") { if (begin == end) throw std::runtime_error(_("Invalid xact command arguments")); tmpl->note = (*++begin).to_string(); } else if (arg == "rest") { ; // just ignore this argument } else if (arg == "@" || arg == "@@") { amount_t cost; post->cost_operator = arg; if (begin == end) throw std::runtime_error(_("Invalid xact command arguments")); arg = (*++begin).to_string(); if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) throw std::runtime_error(_("Invalid xact command arguments")); post->cost = cost; } else { // Without a preposition, it is either: // // A payee, if we have not seen one // An account or an amount, if we have // An account if an amount has just been seen // An amount if an account has just been seen if (tmpl->payee_mask.empty()) { tmpl->payee_mask = arg; } else { amount_t amt; optional account; if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) account = mask_t(arg); if (! post || (account && post->account_mask) || (! account && post->amount)) { tmpl->posts.push_back(xact_template_t::post_template_t()); post = &tmpl->posts.back(); } if (account) { post->account_mask = account; } else { post->amount = amt; post = NULL; // an amount concludes this posting } } } } } if (! tmpl->posts.empty()) { bool has_only_from = true; bool has_only_to = true; // A single account at the end of the line is the "from" account if (tmpl->posts.size() > 1 && tmpl->posts.back().account_mask && ! tmpl->posts.back().amount) tmpl->posts.back().from = true; foreach (xact_template_t::post_template_t& post_tmpl, tmpl->posts) { if (post_tmpl.from) has_only_to = false; else has_only_from = false; } if (has_only_from) { tmpl->posts.push_front(xact_template_t::post_template_t()); } else if (has_only_to) { tmpl->posts.push_back(xact_template_t::post_template_t()); tmpl->posts.back().from = true; } } } xact_t * draft_t::insert(journal_t& journal) { if (! tmpl) return NULL; if (tmpl->payee_mask.empty()) throw std::runtime_error(_("'xact' command requires at least a payee")); xact_t * matching = NULL; unique_ptr added(new xact_t); if (xact_t * xact = lookup_probable_account(tmpl->payee_mask.str(), journal.xacts.rbegin(), journal.xacts.rend()).first) { DEBUG("draft.xact", "Found payee by lookup: transaction on line " << xact->pos->beg_line); matching = xact; } else { for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); j != journal.xacts.rend(); j++) { if (tmpl->payee_mask.match((*j)->payee)) { matching = *j; DEBUG("draft.xact", "Found payee match: transaction on line " << (*j)->pos->beg_line); break; } } } if (! tmpl->date) { added->_date = CURRENT_DATE(); DEBUG("draft.xact", "Setting date to current date"); } else { added->_date = tmpl->date; DEBUG("draft.xact", "Setting date to template date: " << *tmpl->date); } added->set_state(item_t::UNCLEARED); if (matching) { added->payee = matching->payee; //added->code = matching->code; //added->note = matching->note; #if DEBUG_ON DEBUG("draft.xact", "Setting payee from match: " << added->payee); //if (added->code) // DEBUG("draft.xact", "Setting code from match: " << *added->code); //if (added->note) // DEBUG("draft.xact", "Setting note from match: " << *added->note); #endif } else { added->payee = tmpl->payee_mask.str(); DEBUG("draft.xact", "Setting payee from template: " << added->payee); } if (tmpl->code) { added->code = tmpl->code; DEBUG("draft.xact", "Now setting code from template: " << *added->code); } if (tmpl->note) { added->note = tmpl->note; DEBUG("draft.xact", "Now setting note from template: " << *added->note); } if (tmpl->posts.empty()) { if (matching) { DEBUG("draft.xact", "Template had no postings, copying from match"); foreach (post_t * post, matching->posts) { added->add_post(new post_t(*post)); added->posts.back()->set_state(item_t::UNCLEARED); } } else { throw_(std::runtime_error, _f("No accounts, and no past transaction matching '%1%'") % tmpl->payee_mask); } } else { DEBUG("draft.xact", "Template had postings"); bool any_post_has_amount = false; foreach (xact_template_t::post_template_t& post, tmpl->posts) { if (post.amount) { DEBUG("draft.xact", " and at least one has an amount specified"); any_post_has_amount = true; break; } } foreach (xact_template_t::post_template_t& post, tmpl->posts) { unique_ptr new_post; commodity_t * found_commodity = NULL; if (matching) { if (post.account_mask) { DEBUG("draft.xact", "Looking for matching posting based on account mask"); foreach (post_t * x, matching->posts) { if (post.account_mask->match(x->account->fullname())) { new_post.reset(new post_t(*x)); DEBUG("draft.xact", "Founding posting from line " << x->pos->beg_line); break; } } } else { if (post.from) { for (posts_list::reverse_iterator j = matching->posts.rbegin(); j != matching->posts.rend(); j++) { if ((*j)->must_balance()) { new_post.reset(new post_t(**j)); DEBUG("draft.xact", "Copied last real posting from matching"); break; } } } else { for (posts_list::iterator j = matching->posts.begin(); j != matching->posts.end(); j++) { if ((*j)->must_balance()) { new_post.reset(new post_t(**j)); DEBUG("draft.xact", "Copied first real posting from matching"); break; } } } } } if (! new_post.get()) { new_post.reset(new post_t); DEBUG("draft.xact", "New posting was NULL, creating a blank one"); } if (! new_post->account) { DEBUG("draft.xact", "New posting still needs an account"); if (post.account_mask) { DEBUG("draft.xact", "The template has an account mask"); account_t * acct = NULL; if (! acct) { acct = journal.find_account_re(post.account_mask->str()); #if DEBUG_ON if (acct) DEBUG("draft.xact", "Found account as a regular expression"); #endif } if (! acct) { acct = journal.find_account(post.account_mask->str()); #if DEBUG_ON if (acct) DEBUG("draft.xact", "Found (or created) account by name"); #endif } // Find out the default commodity to use by looking at the last // commodity used in that account for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); j != journal.xacts.rend(); j++) { foreach (post_t * x, (*j)->posts) { if (x->account == acct && ! x->amount.is_null()) { new_post.reset(new post_t(*x)); DEBUG("draft.xact", "Found account in journal postings, setting new posting"); break; } } } new_post->account = acct; DEBUG("draft.xact", "Set new posting's account to: " << acct->fullname()); } else { if (post.from) { new_post->account = journal.find_account(_("Liabilities:Unknown")); DEBUG("draft.xact", "Set new posting's account to: Liabilities:Unknown"); } else { new_post->account = journal.find_account(_("Expenses:Unknown")); DEBUG("draft.xact", "Set new posting's account to: Expenses:Unknown"); } } } assert(new_post->account); if (new_post.get() && ! new_post->amount.is_null()) { found_commodity = &new_post->amount.commodity(); if (any_post_has_amount) { new_post->amount = amount_t(); DEBUG("draft.xact", "New posting has an amount, but we cleared it"); } else { any_post_has_amount = true; DEBUG("draft.xact", "New posting has an amount, and we're using it"); } } if (post.amount) { new_post->amount = *post.amount; DEBUG("draft.xact", "Copied over posting amount"); if (post.from) { new_post->amount.in_place_negate(); DEBUG("draft.xact", "Negated new posting amount"); } } if (post.cost) { if (post.cost->sign() < 0) throw parse_error(_("A posting's cost may not be negative")); post.cost->in_place_unround(); if (*post.cost_operator == "@") { // For the sole case where the cost might be uncommoditized, // guarantee that the commodity of the cost after multiplication // is the same as it was before. commodity_t& cost_commodity(post.cost->commodity()); *post.cost *= new_post->amount; post.cost->set_commodity(cost_commodity); } else if (new_post->amount.sign() < 0) { new_post->cost->in_place_negate(); } new_post->cost = *post.cost; DEBUG("draft.xact", "Copied over posting cost"); } if (found_commodity && ! new_post->amount.is_null() && ! new_post->amount.has_commodity()) { new_post->amount.set_commodity(*found_commodity); DEBUG("draft.xact", "Set posting amount commodity to: " << new_post->amount.commodity()); new_post->amount = new_post->amount.rounded(); DEBUG("draft.xact", "Rounded posting amount to: " << new_post->amount); } added->add_post(new_post.release()); added->posts.back()->account->add_post(added->posts.back()); added->posts.back()->set_state(item_t::UNCLEARED); DEBUG("draft.xact", "Added new posting to derived entry"); } } if (! journal.add_xact(added.get())) throw_(std::runtime_error, _("Failed to finalize derived transaction (check commodities)")); return added.release(); } value_t template_command(call_scope_t& args) { report_t& report(find_scope(args)); std::ostream& out(report.output_stream); out << _("--- Input arguments ---") << std::endl; args.value().dump(out); out << std::endl << std::endl; draft_t draft(args.value()); out << _("--- Transaction template ---") << std::endl; draft.dump(out); return true; } value_t xact_command(call_scope_t& args) { report_t& report(find_scope(args)); draft_t draft(args.value()); unique_ptr new_xact(draft.insert(*report.session.journal.get())); if (new_xact.get()) { // Only consider actual postings for the "xact" command report.HANDLER(limit_).on("#xact", "actual"); report.xact_report(post_handler_ptr(new print_xacts(report)), *new_xact.get()); } return true; } } // namespace ledger ledger-3.1.1+dfsg1/src/draft.h000066400000000000000000000067111266023441000160210ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup expr */ /** * @file draft.h * @author John Wiegley * * @ingroup report */ #ifndef _DRAFT_H #define _DRAFT_H #include "exprbase.h" #include "value.h" namespace ledger { class journal_t; class xact_t; class draft_t : public expr_base_t { typedef expr_base_t base_type; struct xact_template_t { optional date; optional code; optional note; mask_t payee_mask; struct post_template_t { bool from; optional account_mask; optional amount; optional cost_operator; optional cost; post_template_t() : from(false) { TRACE_CTOR(post_template_t, ""); } ~post_template_t() throw() { TRACE_DTOR(post_template_t); } }; std::list posts; xact_template_t() { TRACE_CTOR(xact_template_t, ""); } xact_template_t(const xact_template_t& other) : date(other.date), code(other.code), note(other.note), payee_mask(other.payee_mask), posts(other.posts) { TRACE_CTOR(xact_template_t, "copy"); } ~xact_template_t() throw() { TRACE_DTOR(xact_template_t); } void dump(std::ostream& out) const; }; optional tmpl; public: draft_t(const value_t& args) : base_type() { if (! args.empty()) parse_args(args); TRACE_CTOR(draft_t, "value_t"); } virtual ~draft_t() throw() { TRACE_DTOR(draft_t); } void parse_args(const value_t& args); virtual result_type real_calc(scope_t&) { assert(false); return true; } xact_t * insert(journal_t& journal); virtual void dump(std::ostream& out) const { if (tmpl) tmpl->dump(out); } }; value_t xact_command(call_scope_t& args); value_t template_command(call_scope_t& args); } // namespace ledger #endif // _DRAFT_H ledger-3.1.1+dfsg1/src/emacs.cc000066400000000000000000000065221266023441000161470ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "emacs.h" #include "xact.h" #include "post.h" #include "account.h" namespace ledger { void format_emacs_posts::write_xact(xact_t& xact) { if (xact.pos) out << "\"" << xact.pos->pathname.string() << "\" " << xact.pos->beg_line << " "; else out << "\"\" " << -1 << " "; tm when = gregorian::to_tm(xact.date()); std::time_t date = std::mktime(&when); out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; if (xact.code) out << "\"" << *xact.code << "\" "; else out << "nil "; if (xact.payee.empty()) out << "nil"; else out << "\"" << xact.payee << "\""; out << "\n"; } void format_emacs_posts::operator()(post_t& post) { if (! post.has_xdata() || ! post.xdata().has_flags(POST_EXT_DISPLAYED)) { if (! last_xact) { out << "(("; write_xact(*post.xact); } else if (post.xact != last_xact) { out << ")\n ("; write_xact(*post.xact); } else { out << "\n"; } if (post.pos) out << " (" << post.pos->beg_line << " "; else out << " (" << -1 << " "; out << "\"" << post.reported_account()->fullname() << "\" \"" << post.amount << "\""; switch (post.state()) { case item_t::UNCLEARED: out << " nil"; break; case item_t::CLEARED: out << " t"; break; case item_t::PENDING: out << " pending"; break; } if (post.cost) out << " \"" << *post.cost << "\""; if (post.note) out << " \"" << escape_string(*post.note) << "\""; out << ")"; last_xact = post.xact; post.xdata().add_flags(POST_EXT_DISPLAYED); } } string format_emacs_posts::escape_string(string raw){ replace_all(raw, "\\", "\\\\"); replace_all(raw, "\"", "\\\""); return raw; } } // namespace ledger ledger-3.1.1+dfsg1/src/emacs.h000066400000000000000000000045151266023441000160110ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup report */ /** * @file emacs.h * @author John Wiegley * * @ingroup report */ #ifndef _EMACS_H #define _EMACS_H #include "chain.h" namespace ledger { class xact_t; class format_emacs_posts : public item_handler { format_emacs_posts(); protected: std::ostream& out; xact_t * last_xact; public: format_emacs_posts(std::ostream& _out) : out(_out), last_xact(NULL) { TRACE_CTOR(format_emacs_posts, "std::ostream&"); } ~format_emacs_posts() { TRACE_DTOR(format_emacs_posts); } virtual void write_xact(xact_t& xact); virtual void flush() { if (last_xact) out << "))\n"; out.flush(); } virtual void operator()(post_t& post); virtual string escape_string(string raw); }; } // namespace ledger #endif // _REPORT_H ledger-3.1.1+dfsg1/src/error.cc000066400000000000000000000066611266023441000162140ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "utils.h" namespace ledger { std::ostringstream _ctxt_buffer; std::ostringstream _desc_buffer; string error_context() { string context = _ctxt_buffer.str(); _ctxt_buffer.clear(); _ctxt_buffer.str(""); return context; } string file_context(const path& file, const std::size_t line) { std::ostringstream buf; buf << '"' << file.string() << "\", line " << line << ":"; return buf.str(); } string line_context(const string& line, const string::size_type pos, const string::size_type end_pos) { std::ostringstream buf; buf << " " << line << "\n"; if (pos != 0) { buf << " "; if (end_pos == 0) { for (string::size_type i = 0; i < pos; i += 1) buf << " "; buf << "^"; } else { for (string::size_type i = 0; i < end_pos; i += 1) { if (i >= pos) buf << "^"; else buf << " "; } } } return buf.str(); } string source_context(const path& file, const istream_pos_type pos, const istream_pos_type end_pos, const string& prefix) { const std::streamoff len = end_pos - pos; if (! len || file.empty()) return _(""); assert(len > 0); assert(len < 8192); std::ostringstream out; ifstream in(file); in.seekg(pos, std::ios::beg); scoped_array buf(new char[static_cast(len) + 1]); in.read(buf.get(), static_cast(len)); assert(in.gcount() == static_cast(len)); buf[static_cast(len)] = '\0'; bool first = true; for (char * p = std::strtok(buf.get(), "\n"); p; p = std::strtok(NULL, "\n")) { if (first) first = false; else out << '\n'; out << prefix << p; } return out.str(); } } // namespace ledger ledger-3.1.1+dfsg1/src/error.h000066400000000000000000000067641266023441000160620ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup util */ /** * @file error.h * @author John Wiegley * * @ingroup util */ #ifndef _ERROR_H #define _ERROR_H namespace ledger { extern std::ostringstream _desc_buffer; template inline void throw_func(const string& message) { _desc_buffer.clear(); _desc_buffer.str(""); throw T(message); } #define throw_(cls, msg) \ ((_desc_buffer << (msg)), \ throw_func(_desc_buffer.str())) inline void warning_func(const string& message) { std::cerr << "Warning: " << message << std::endl; _desc_buffer.clear(); _desc_buffer.str(""); } #define warning_(msg) \ ((_desc_buffer << (msg)), \ warning_func(_desc_buffer.str())) extern std::ostringstream _ctxt_buffer; #define add_error_context(msg) \ ((long(_ctxt_buffer.tellp()) == 0) ? \ (_ctxt_buffer << (msg)) : \ (_ctxt_buffer << std::endl << (msg))) string error_context(); string file_context(const path& file, std::size_t line); string line_context(const string& line, const string::size_type pos = 0, const string::size_type end_pos = 0); string source_context(const path& file, const istream_pos_type pos, const istream_pos_type end_pos, const string& prefix = ""); #define DECLARE_EXCEPTION(name, kind) \ class name : public kind { \ public: \ explicit name(const string& why) throw() : kind(why) {} \ virtual ~name() throw() {} \ } struct error_count { std::size_t count; explicit error_count(std::size_t _count) : count(_count) {} const char * what() const { return ""; } }; } // namespace ledger #endif // _ERROR_H ledger-3.1.1+dfsg1/src/expr.cc000066400000000000000000000166431266023441000160420ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "expr.h" #include "parser.h" #include "scope.h" namespace ledger { expr_t::expr_t() : base_type() { TRACE_CTOR(expr_t, ""); } expr_t::expr_t(const expr_t& other) : base_type(other), ptr(other.ptr) { TRACE_CTOR(expr_t, "copy"); } expr_t::expr_t(ptr_op_t _ptr, scope_t * _context) : base_type(_context), ptr(_ptr) { TRACE_CTOR(expr_t, "const ptr_op_t&, scope_t *"); } expr_t::expr_t(const string& _str, const parse_flags_t& flags) : base_type() { if (! _str.empty()) parse(_str, flags); TRACE_CTOR(expr_t, "string, parse_flags_t"); } expr_t::expr_t(std::istream& in, const parse_flags_t& flags) : base_type() { parse(in, flags); TRACE_CTOR(expr_t, "std::istream&, parse_flags_t"); } expr_t::~expr_t() { TRACE_DTOR(expr_t); } expr_t& expr_t::operator=(const expr_t& _expr) { if (this != &_expr) { base_type::operator=(_expr); ptr = _expr.ptr; } return *this; } expr_t::operator bool() const throw() { return ptr.get() != NULL; } expr_t::ptr_op_t expr_t::get_op() throw() { return ptr; } void expr_t::parse(std::istream& in, const parse_flags_t& flags, const optional& original_string) { parser_t parser; istream_pos_type start_pos = in.tellg(); ptr = parser.parse(in, flags, original_string); istream_pos_type end_pos = in.tellg(); if (original_string) { set_text(*original_string); } else if (end_pos > start_pos) { in.clear(); in.seekg(start_pos, std::ios::beg); scoped_array buf (new char[static_cast(end_pos - start_pos) + 1]); int len = static_cast(end_pos) - static_cast(start_pos); in.read(buf.get(), len); buf[len] = '\0'; set_text(buf.get()); } else { set_text(""); } } void expr_t::compile(scope_t& scope) { if (! compiled && ptr) { ptr = ptr->compile(scope); base_type::compile(scope); } } value_t expr_t::real_calc(scope_t& scope) { if (ptr) { ptr_op_t locus; try { return ptr->calc(scope, &locus); } catch (const std::exception&) { if (locus) { string current_context = error_context(); add_error_context(_("While evaluating value expression:")); add_error_context(op_context(ptr, locus)); if (SHOW_INFO()) { add_error_context(_("The value expression tree was:")); std::ostringstream buf; ptr->dump(buf, 0); std::istringstream in(buf.str()); std::ostringstream out; char linebuf[1024]; bool first = true; while (in.good() && ! in.eof()) { in.getline(linebuf, 1023); std::streamsize len = in.gcount(); if (len > 0) { if (first) first = false; else out << '\n'; out << " " << linebuf; } } add_error_context(out.str()); } if (! current_context.empty()) add_error_context(current_context); } throw; } } return NULL_VALUE; } bool expr_t::is_constant() const { assert(compiled); return ptr && ptr->is_value(); } bool expr_t::is_function() const { assert(compiled); return ptr && ptr->is_function(); } value_t& expr_t::constant_value() { assert(is_constant()); return ptr->as_value_lval(); } const value_t& expr_t::constant_value() const { assert(is_constant()); return ptr->as_value(); } expr_t::func_t& expr_t::get_function() { assert(is_function()); return ptr->as_function_lval(); } string expr_t::context_to_str() const { return ptr ? op_context(ptr) : _(""); } void expr_t::print(std::ostream& out) const { if (ptr) ptr->print(out); } void expr_t::dump(std::ostream& out) const { if (ptr) ptr->dump(out, 0); } bool merged_expr_t::check_for_single_identifier(const string& expr) { bool single_identifier = true; for (const char * p = expr.c_str(); *p; ++p) if (! std::isalnum(*p) || *p == '_') { single_identifier = false; break; } if (single_identifier) { set_base_expr(expr); exprs.clear(); return true; } else { return false; } } void merged_expr_t::compile(scope_t& scope) { if (exprs.empty()) { parse(base_expr); } else { std::ostringstream buf; buf << "__tmp_" << term << "=(" << term << "=(" << base_expr << ")"; foreach (const string& expr, exprs) { if (merge_operator == ";") buf << merge_operator << term << "=" << expr; else buf << merge_operator << "(" << expr << ")"; } buf << ";" << term << ");__tmp_" << term; DEBUG("expr.merged.compile", "Compiled expr: " << buf.str()); parse(buf.str()); } expr_t::compile(scope); } expr_t::ptr_op_t as_expr(const value_t& val) { VERIFY(val.is_any()); return val.as_any(); } void set_expr(value_t& val, expr_t::ptr_op_t op) { val.set_any(op); } value_t expr_value(expr_t::ptr_op_t op) { value_t temp; temp.set_any(op); return temp; } value_t source_command(call_scope_t& args) { std::istream * in = NULL; scoped_ptr stream; string pathname; if (args.has(0)) { pathname = args.get(0); stream.reset(new ifstream(path(pathname))); in = stream.get(); } else { pathname = ""; in = &std::cin; } symbol_scope_t file_locals(args); std::size_t linenum = 0; char buf[4096]; istream_pos_type pos; while (in->good() && ! in->eof()) { pos = in->tellg(); in->getline(buf, 4095); linenum++; char * p = skip_ws(buf); if (*p && *p != ';') { try { expr_t(p).calc(file_locals); } catch (const std::exception&) { add_error_context(_f("While parsing value expression on line %1%:") % linenum); add_error_context(source_context(pathname, pos, in->tellg(), "> ")); } } } return true; } } // namespace ledger ledger-3.1.1+dfsg1/src/expr.h000066400000000000000000000125021266023441000156720ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup expr */ /** * @file expr.h * @author John Wiegley * * @ingroup expr */ #ifndef _EXPR_H #define _EXPR_H #include "exprbase.h" #include "value.h" namespace ledger { class expr_t : public expr_base_t { class parser_t; typedef expr_base_t base_type; public: struct token_t; class op_t; typedef intrusive_ptr ptr_op_t; typedef intrusive_ptr const_ptr_op_t; enum check_expr_kind_t { EXPR_GENERAL, EXPR_ASSERTION, EXPR_CHECK }; typedef std::pair check_expr_pair; typedef std::list check_expr_list; protected: ptr_op_t ptr; public: expr_t(); expr_t(const expr_t& other); expr_t(ptr_op_t _ptr, scope_t * _context = NULL); expr_t(const string& _str, const parse_flags_t& flags = PARSE_DEFAULT); expr_t(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT); virtual ~expr_t(); expr_t& operator=(const expr_t& _expr); virtual operator bool() const throw(); ptr_op_t get_op() throw(); void parse(const string& str, const parse_flags_t& flags = PARSE_DEFAULT) { std::istringstream stream(str); return parse(stream, flags, str); } virtual void parse(std::istream& in, const parse_flags_t& flags = PARSE_DEFAULT, const optional& original_string = none); virtual void compile(scope_t& scope); virtual value_t real_calc(scope_t& scope); bool is_constant() const; value_t& constant_value(); const value_t& constant_value() const; bool is_function() const; func_t& get_function(); virtual string context_to_str() const; virtual void print(std::ostream& out) const; virtual void dump(std::ostream& out) const; }; /** * Dealing with expr pointers tucked into value objects. */ inline bool is_expr(const value_t& val) { return val.is_any() && val.as_any().type() == typeid(expr_t::ptr_op_t); } expr_t::ptr_op_t as_expr(const value_t& val); void set_expr(value_t& val, expr_t::ptr_op_t op); value_t expr_value(expr_t::ptr_op_t op); // A merged expression allows one to set an expression term, "foo", and // a base expression, "bar", and then merge in later expressions that // utilize foo. For example: // // foo: bar // merge: foo * 10 // merge: foo + 20 // // When this expression is finally compiled, the base and merged // elements are written into this: // // __tmp=(foo=bar; foo=foo*10; foo=foo+20);__tmp // // This allows users to select flags like -O, -B or -I at any time, and // also combine flags such as -V and -A. class merged_expr_t : public expr_t { public: string term; string base_expr; string merge_operator; std::list exprs; merged_expr_t(const string& _term, const string& expr, const string& merge_op = ";") : expr_t(), term(_term), base_expr(expr), merge_operator(merge_op) { TRACE_CTOR(merged_expr_t, "string, string, string"); } virtual ~merged_expr_t() { TRACE_DTOR(merged_expr_t); } void set_term(const string& _term) { term = _term; } void set_base_expr(const string& expr) { base_expr = expr; } void set_merge_operator(const string& merge_op) { merge_operator = merge_op; } bool check_for_single_identifier(const string& expr); void prepend(const string& expr) { if (! check_for_single_identifier(expr)) exprs.push_front(expr); } void append(const string& expr) { if (! check_for_single_identifier(expr)) exprs.push_back(expr); } void remove(const string& expr) { exprs.remove(expr); } virtual void compile(scope_t& scope); }; class call_scope_t; value_t source_command(call_scope_t& scope); } // namespace ledger #endif // _EXPR_H ledger-3.1.1+dfsg1/src/exprbase.h000066400000000000000000000150411266023441000165260ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup expr */ /** * @file exprbase.h * @author John Wiegley * * @ingroup expr * * This class provides basic behavior for all the domain specific expression * languages used in Leger: * * | Typename | Description | result_type | Derives | * |-------------+----------------------------+-----------------+-------------| * | expr_t | Value expressions | value_t | | * | predicate_t | Special form of expr_t | bool | expr_t | * | query_t | Report queries | bool | predicate_t | * | period_t | Time periods and durations | date_interval_t | | * | draft_t | Partially filled xacts | xact_t * | | * | format_t | Format strings | string | | */ #ifndef _EXPRBASE_H #define _EXPRBASE_H #include "utils.h" #include "amount.h" namespace ledger { DECLARE_EXCEPTION(parse_error, std::runtime_error); DECLARE_EXCEPTION(compile_error, std::runtime_error); DECLARE_EXCEPTION(calc_error, std::runtime_error); DECLARE_EXCEPTION(usage_error, std::runtime_error); class scope_t; class call_scope_t; template class expr_base_t { public: typedef ResultType result_type; typedef function func_t; protected: scope_t * context; string str; bool compiled; virtual result_type real_calc(scope_t& scope) = 0; public: expr_base_t(const expr_base_t& other) : context(other.context), str(other.str), compiled(false) { TRACE_CTOR(expr_base_t, "copy"); } expr_base_t(scope_t * _context = NULL) : context(_context), compiled(false) { TRACE_CTOR(expr_base_t, "scope_t *"); } virtual ~expr_base_t() { TRACE_DTOR(expr_base_t); } expr_base_t& operator=(const expr_base_t& _expr) { if (this != &_expr) { str = _expr.str; context = _expr.context; compiled = _expr.compiled; } return *this; } expr_base_t& operator=(const string& _expr) { parse(_expr); return *this; } virtual operator bool() const throw() { return ! str.empty(); } virtual string text() const throw() { return str; } void set_text(const string& txt) { str = txt; compiled = false; } void parse(const string& expr_str, const parse_flags_t& flags = PARSE_DEFAULT) { std::istringstream stream(expr_str); return parse(stream, flags, expr_str); } virtual void parse(std::istream&, const parse_flags_t& = PARSE_DEFAULT, const optional& original_string = none) { set_text(original_string ? *original_string : ""); } virtual void mark_uncompiled() { compiled = false; } void recompile(scope_t& scope) { compiled = false; compile(scope); } virtual void compile(scope_t& scope) { if (! compiled) { // Derived classes need to do something here. context = &scope; compiled = true; } } result_type operator()(scope_t& scope) { return calc(scope); } result_type calc(scope_t& scope) { if (! compiled) { #if DEBUG_ON if (SHOW_DEBUG("expr.compile")) { DEBUG("expr.compile", "Before compilation:"); dump(*_log_stream); } #endif // DEBUG_ON DEBUG("expr.compile", "Compiling: " << str); compile(scope); #if DEBUG_ON if (SHOW_DEBUG("expr.compile")) { DEBUG("expr.compile", "After compilation:"); dump(*_log_stream); } #endif // DEBUG_ON } DEBUG("expr.calc", "Calculating: " << str); return real_calc(scope); } result_type calc() { assert(context); return calc(*context); } scope_t * get_context() { return context; } void set_context(scope_t * scope) { context = scope; } virtual string context_to_str() const { return empty_string; } string print_to_str() const { std::ostringstream out; print(out); return out.str(); } string dump_to_str() const { std::ostringstream out; dump(out); return out.str(); } string preview_to_str(scope_t&) const { std::ostringstream out; preview(out); return out.str(); } virtual void print(std::ostream&) const {} virtual void dump(std::ostream&) const {} result_type preview(std::ostream& out, scope_t& scope) const { out << _("--- Input expression ---") << std::endl; out << text() << std::endl; out << std::endl << _("--- Text as parsed ---") << std::endl; print(out); out << std::endl; out << std::endl << _("--- Expression tree ---") << std::endl; dump(out); out << std::endl << _("--- Compiled tree ---") << std::endl; compile(scope); dump(out); out << std::endl << _("--- Result value ---") << std::endl; return calc(); } }; template std::ostream& operator<<(std::ostream& out, const expr_base_t& expr) { expr.print(out); return out; } } // namespace ledger #endif // _EXPRBASE_H ledger-3.1.1+dfsg1/src/filters.cc000066400000000000000000001322511266023441000165260ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "filters.h" #include "iterators.h" #include "journal.h" #include "report.h" #include "compare.h" #include "pool.h" namespace ledger { void post_splitter::print_title(const value_t& val) { if (! report.HANDLED(no_titles)) { std::ostringstream buf; val.print(buf); post_chain->title(buf.str()); } } void post_splitter::flush() { foreach (value_to_posts_map::value_type& pair, posts_map) { preflush_func(pair.first); foreach (post_t * post, pair.second) (*post_chain)(*post); post_chain->flush(); post_chain->clear(); if (postflush_func) (*postflush_func)(pair.first); } } void post_splitter::operator()(post_t& post) { bind_scope_t bound_scope(report, post); value_t result(group_by_expr.calc(bound_scope)); if (! result.is_null()) { value_to_posts_map::iterator i = posts_map.find(result); if (i != posts_map.end()) { (*i).second.push_back(&post); } else { std::pair inserted = posts_map.insert(value_to_posts_map::value_type(result, posts_list())); assert(inserted.second); (*inserted.first).second.push_back(&post); } } } void truncate_xacts::flush() { if (! posts.size()) return; xact_t * xact = (*posts.begin())->xact; int l = 0; foreach (post_t * post, posts) if (xact != post->xact) { l++; xact = post->xact; } l++; xact = (*posts.begin())->xact; int i = 0; foreach (post_t * post, posts) { if (xact != post->xact) { xact = post->xact; i++; } bool print = false; if (head_count) { if (head_count > 0 && i < head_count) print = true; else if (head_count < 0 && i >= - head_count) print = true; } if (! print && tail_count) { if (tail_count > 0 && l - i <= tail_count) print = true; else if (tail_count < 0 && l - i > - tail_count) print = true; } if (print) item_handler::operator()(*post); } posts.clear(); item_handler::flush(); } void truncate_xacts::operator()(post_t& post) { if (completed) return; if (last_xact != post.xact) { if (last_xact) xacts_seen++; last_xact = post.xact; } if (tail_count == 0 && head_count > 0 && static_cast(xacts_seen) >= head_count) { flush(); completed = true; return; } posts.push_back(&post); } void sort_posts::post_accumulated_posts() { std::stable_sort(posts.begin(), posts.end(), compare_items(sort_order)); foreach (post_t * post, posts) { post->xdata().drop_flags(POST_EXT_SORT_CALC); item_handler::operator()(*post); } posts.clear(); } namespace { void split_string(const string& str, const char ch, std::list& strings) { const char * b = str.c_str(); for (const char * p = b; *p; p++) { if (*p == ch) { strings.push_back(string(b, static_cast(p - b))); b = p + 1; } } strings.push_back(string(b)); } account_t * create_temp_account_from_path(std::list& account_names, temporaries_t& temps, account_t * master) { account_t * new_account = NULL; foreach (const string& name, account_names) { if (new_account) { new_account = new_account->find_account(name); } else { new_account = master->find_account(name, false); if (! new_account) new_account = &temps.create_account(name, master); } } assert(new_account != NULL); return new_account; } } void anonymize_posts::render_commodity(amount_t& amt) { commodity_t& comm(amt.commodity()); std::size_t id; bool newly_added = false; commodity_index_map::iterator i = comms.find(&comm); if (i == comms.end()) { id = next_comm_id++; newly_added = true; comms.insert(commodity_index_map::value_type(&comm, id)); } else { id = (*i).second; } std::ostringstream buf; do { buf << static_cast('A' + (id % 26)); id /= 26; } while (id > 0); if (amt.has_annotation()) amt.set_commodity (*commodity_pool_t::current_pool->find_or_create(buf.str(), amt.annotation())); else amt.set_commodity (*commodity_pool_t::current_pool->find_or_create(buf.str())); if (newly_added) { amt.commodity().set_flags(comm.flags()); amt.commodity().set_precision(comm.precision()); } } void anonymize_posts::operator()(post_t& post) { boost::uuids::detail::sha1 sha; unsigned int message_digest[5]; bool copy_xact_details = false; if (last_xact != post.xact) { temps.copy_xact(*post.xact); last_xact = post.xact; copy_xact_details = true; } xact_t& xact = temps.last_xact(); xact.code = none; if (copy_xact_details) { xact.copy_details(*post.xact); std::ostringstream buf; buf << reinterpret_cast(post.xact->payee.c_str()) << integer_gen() << post.xact->payee.c_str(); sha.reset(); sha.process_bytes(buf.str().c_str(), buf.str().length()); sha.get_digest(message_digest); xact.payee = to_hex(message_digest); xact.note = none; } else { xact.journal = post.xact->journal; } std::list account_names; for (account_t * acct = post.account; acct; acct = acct->parent) { std::ostringstream buf; buf << integer_gen() << acct << acct->fullname(); sha.reset(); sha.process_bytes(buf.str().c_str(), buf.str().length()); sha.get_digest(message_digest); account_names.push_front(to_hex(message_digest)); } account_t * new_account = create_temp_account_from_path(account_names, temps, xact.journal->master); post_t& temp = temps.copy_post(post, xact, new_account); temp.note = none; temp.add_flags(POST_ANONYMIZED); render_commodity(temp.amount); if (temp.amount.has_annotation()) { temp.amount.annotation().tag = none; if (temp.amount.annotation().price) render_commodity(*temp.amount.annotation().price); } if (temp.cost) render_commodity(*temp.cost); if (temp.assigned_amount) render_commodity(*temp.assigned_amount); (*handler)(temp); } void calc_posts::operator()(post_t& post) { post_t::xdata_t& xdata(post.xdata()); if (last_post) { assert(last_post->has_xdata()); if (calc_running_total) xdata.total = last_post->xdata().total; xdata.count = last_post->xdata().count + 1; } else { xdata.count = 1; } post.add_to_value(xdata.visited_value, amount_expr); xdata.add_flags(POST_EXT_VISITED); account_t * acct = post.reported_account(); acct->xdata().add_flags(ACCOUNT_EXT_VISITED); if (calc_running_total) add_or_set_value(xdata.total, xdata.visited_value); item_handler::operator()(post); last_post = &post; } namespace { void handle_value(const value_t& value, account_t * account, xact_t * xact, temporaries_t& temps, post_handler_ptr handler, const date_t& date = date_t(), const bool act_date_p = true, const value_t& total = value_t(), const bool direct_amount = false, const bool mark_visited = false, const bool bidir_link = true) { post_t& post = temps.create_post(*xact, account, bidir_link); post.add_flags(ITEM_GENERATED); // If the account for this post is all virtual, then report the post as // such. This allows subtotal reports to show "(Account)" for accounts // that contain only virtual posts. if (account && account->has_xdata() && account->xdata().has_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE)) { if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS)) { post.add_flags(POST_VIRTUAL); if (! account->xdata().has_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS)) post.add_flags(POST_MUST_BALANCE); } } post_t::xdata_t& xdata(post.xdata()); if (is_valid(date)) { if (act_date_p) xdata.date = date; else xdata.value_date = date; } value_t temp(value); switch (value.type()) { case value_t::BOOLEAN: case value_t::INTEGER: temp.in_place_cast(value_t::AMOUNT); // fall through... case value_t::AMOUNT: post.amount = temp.as_amount(); break; case value_t::BALANCE: case value_t::SEQUENCE: xdata.compound_value = temp; xdata.add_flags(POST_EXT_COMPOUND); break; case value_t::DATETIME: case value_t::DATE: default: assert(false); break; } if (! total.is_null()) xdata.total = total; if (direct_amount) xdata.add_flags(POST_EXT_DIRECT_AMT); DEBUG("filters.changed_value.rounding", "post.amount = " << post.amount); (*handler)(post); if (mark_visited) { post.xdata().add_flags(POST_EXT_VISITED); post.account->xdata().add_flags(ACCOUNT_EXT_VISITED); } } } void collapse_posts::report_subtotal() { if (! count) return; std::size_t displayed_count = 0; foreach (post_t * post, component_posts) { bind_scope_t bound_scope(report, *post); if (only_predicate(bound_scope) && display_predicate(bound_scope)) displayed_count++; } if (displayed_count == 1) { item_handler::operator()(*last_post); } else if (only_collapse_if_zero && ! subtotal.is_zero()) { foreach (post_t * post, component_posts) item_handler::operator()(*post); } else { date_t earliest_date; date_t latest_date; foreach (post_t * post, component_posts) { date_t date = post->date(); date_t value_date = post->value_date(); if (! is_valid(earliest_date) || date < earliest_date) earliest_date = date; if (! is_valid(latest_date) || value_date > latest_date) latest_date = value_date; } xact_t& xact = temps.create_xact(); xact.payee = last_xact->payee; xact._date = (is_valid(earliest_date) ? earliest_date : last_xact->_date); DEBUG("filters.collapse", "Pseudo-xact date = " << *xact._date); DEBUG("filters.collapse", "earliest date = " << earliest_date); DEBUG("filters.collapse", "latest date = " << latest_date); handle_value(/* value= */ subtotal, /* account= */ totals_account, /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ latest_date, /* act_date_p= */ false); } component_posts.clear(); last_xact = NULL; last_post = NULL; subtotal = 0L; count = 0; } void collapse_posts::operator()(post_t& post) { // If we've reached a new xact, report on the subtotal // accumulated thus far. if (last_xact != post.xact && count > 0) report_subtotal(); post.add_to_value(subtotal, amount_expr); component_posts.push_back(&post); last_xact = post.xact; last_post = &post; count++; } void related_posts::flush() { if (posts.size() > 0) { foreach (post_t * post, posts) { assert(post->xact); foreach (post_t * r_post, post->xact->posts) { post_t::xdata_t& xdata(r_post->xdata()); if (! xdata.has_flags(POST_EXT_HANDLED) && (! xdata.has_flags(POST_EXT_RECEIVED) ? ! r_post->has_flags(ITEM_GENERATED | POST_VIRTUAL) : also_matching)) { xdata.add_flags(POST_EXT_HANDLED); item_handler::operator()(*r_post); } } } } item_handler::flush(); } display_filter_posts::display_filter_posts(post_handler_ptr handler, report_t& _report, bool _show_rounding) : item_handler(handler), report(_report), display_amount_expr(report.HANDLER(display_amount_).expr), display_total_expr(report.HANDLER(display_total_).expr), show_rounding(_show_rounding) { create_accounts(); TRACE_CTOR(display_filter_posts, "post_handler_ptr, report_t&, bool"); } bool display_filter_posts::output_rounding(post_t& post) { bind_scope_t bound_scope(report, post); value_t new_display_total; if (show_rounding) { new_display_total = (display_total_expr.calc(bound_scope) .strip_annotations(report.what_to_keep())); DEBUG("filters.changed_value.rounding", "rounding.new_display_total = " << new_display_total); } // Allow the posting to be displayed if: // 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 , and // b) the revalued option is specified, and // c) the --no-rounding option is not specified. if (post.account == revalued_account) { if (show_rounding) last_display_total = new_display_total; return true; } if (value_t repriced_amount = (display_amount_expr.calc(bound_scope) .strip_annotations(report.what_to_keep()))) { if (! last_display_total.is_null()) { DEBUG("filters.changed_value.rounding", "rounding.repriced_amount = " << repriced_amount); value_t precise_display_total(new_display_total.truncated() - repriced_amount.truncated()); DEBUG("filters.changed_value.rounding", "rounding.precise_display_total = " << precise_display_total); DEBUG("filters.changed_value.rounding", "rounding.last_display_total = " << last_display_total); if (value_t diff = precise_display_total - last_display_total) { DEBUG("filters.changed_value.rounding", "rounding.diff = " << diff); handle_value(/* value= */ diff, /* account= */ rounding_account, /* xact= */ post.xact, /* temps= */ temps, /* handler= */ handler, /* date= */ date_t(), /* act_date_p= */ true, /* total= */ precise_display_total, /* direct_amount= */ true, /* mark_visited= */ false, /* bidir_link= */ false); } } if (show_rounding) last_display_total = new_display_total; return true; } else { return report.HANDLED(empty); } } void display_filter_posts::operator()(post_t& post) { if (output_rounding(post)) item_handler::operator()(post); } changed_value_posts::changed_value_posts (post_handler_ptr handler, report_t& _report, bool _for_accounts_report, bool _show_unrealized, display_filter_posts * _display_filter) : item_handler(handler), report(_report), total_expr(report.HANDLED(revalued_total_) ? report.HANDLER(revalued_total_).expr : report.HANDLER(display_total_).expr), display_total_expr(report.HANDLER(display_total_).expr), changed_values_only(report.HANDLED(revalued_only)), historical_prices_only(report.HANDLED(historical)), for_accounts_report(_for_accounts_report), show_unrealized(_show_unrealized), last_post(NULL), display_filter(_display_filter) { string gains_equity_account_name; if (report.HANDLED(unrealized_gains_)) gains_equity_account_name = report.HANDLER(unrealized_gains_).str(); else gains_equity_account_name = _("Equity:Unrealized Gains"); gains_equity_account = report.session.journal->master->find_account(gains_equity_account_name); gains_equity_account->add_flags(ACCOUNT_GENERATED); string losses_equity_account_name; if (report.HANDLED(unrealized_losses_)) losses_equity_account_name = report.HANDLER(unrealized_losses_).str(); else losses_equity_account_name = _("Equity:Unrealized Losses"); losses_equity_account = report.session.journal->master->find_account(losses_equity_account_name); losses_equity_account->add_flags(ACCOUNT_GENERATED); create_accounts(); TRACE_CTOR(changed_value_posts, "post_handler_ptr, report_t&, bool, bool, display_filter_posts *"); } void changed_value_posts::flush() { if (last_post && last_post->date() <= report.terminus.date()) { if (! historical_prices_only) { if (! for_accounts_report) output_intermediate_prices(*last_post, report.terminus.date()); output_revaluation(*last_post, report.terminus.date()); } last_post = NULL; } item_handler::flush(); } void changed_value_posts::output_revaluation(post_t& post, const date_t& date) { if (is_valid(date)) post.xdata().date = date; try { bind_scope_t bound_scope(report, post); repriced_total = total_expr.calc(bound_scope); } catch (...) { post.xdata().date = date_t(); throw; } post.xdata().date = date_t(); DEBUG("filters.changed_value", "output_revaluation(last_total) = " << last_total); DEBUG("filters.changed_value", "output_revaluation(repriced_total) = " << repriced_total); if (! last_total.is_null()) { if (value_t diff = repriced_total - last_total) { DEBUG("filters.changed_value", "output_revaluation(strip(diff)) = " << diff.strip_annotations(report.what_to_keep())); xact_t& xact = temps.create_xact(); xact.payee = _("Commodities revalued"); xact._date = is_valid(date) ? date : post.value_date(); if (! for_accounts_report) { handle_value (/* value= */ diff, /* account= */ revalued_account, /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ *xact._date, /* act_date_p= */ true, /* total= */ repriced_total); } else if (show_unrealized) { handle_value (/* value= */ - diff, /* account= */ (diff < 0L ? losses_equity_account : gains_equity_account), /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ *xact._date, /* act_date_p= */ true, /* total= */ value_t(), /* direct_amount= */ false, /* mark_visited= */ true); } } } } namespace { struct insert_prices_in_map { price_map_t& all_prices; insert_prices_in_map(price_map_t& _all_prices) : all_prices(_all_prices) {} void operator()(const datetime_t& date, const amount_t& price) { all_prices.insert(price_map_t::value_type(date, price)); } }; } void changed_value_posts::output_intermediate_prices(post_t& post, const date_t& current) { // To fix BZ#199, examine the balance of last_post and determine whether the // price of that amount changed after its date and before the new post's // date. If so, generate an output_revaluation for that price change. // Mostly this is only going to occur if the user has a series of pricing // entries, since a posting-based revaluation would be seen here as a post. value_t display_total(last_total); if (display_total.type() == value_t::SEQUENCE) { xact_t& xact(temps.create_xact()); xact.payee = _("Commodities revalued"); xact._date = is_valid(current) ? current : post.value_date(); post_t& temp(temps.copy_post(post, xact)); temp.add_flags(ITEM_GENERATED); post_t::xdata_t& xdata(temp.xdata()); if (is_valid(current)) xdata.date = current; DEBUG("filters.revalued", "intermediate last_total = " << last_total); switch (last_total.type()) { case value_t::BOOLEAN: case value_t::INTEGER: last_total.in_place_cast(value_t::AMOUNT); // fall through... case value_t::AMOUNT: temp.amount = last_total.as_amount(); break; case value_t::BALANCE: case value_t::SEQUENCE: xdata.compound_value = last_total; xdata.add_flags(POST_EXT_COMPOUND); break; case value_t::DATETIME: case value_t::DATE: default: assert(false); break; } bind_scope_t inner_scope(report, temp); display_total = display_total_expr.calc(inner_scope); DEBUG("filters.revalued", "intermediate display_total = " << display_total); } switch (display_total.type()) { case value_t::VOID: case value_t::INTEGER: case value_t::SEQUENCE: break; case value_t::AMOUNT: display_total.in_place_cast(value_t::BALANCE); // fall through... case value_t::BALANCE: { price_map_t all_prices; foreach (const balance_t::amounts_map::value_type& amt_comm, display_total.as_balance().amounts) amt_comm.first->map_prices(insert_prices_in_map(all_prices), datetime_t(current), datetime_t(post.value_date()), true); // Choose the last price from each day as the price to use typedef std::map date_map; date_map pricing_dates; BOOST_REVERSE_FOREACH(const price_map_t::value_type& price, all_prices) { // This insert will fail if a later price has already been inserted // for that date. DEBUG("filters.revalued", "re-inserting " << price.second << " at " << price.first.date()); pricing_dates.insert(date_map::value_type(price.first.date(), true)); } // Go through the time-sorted prices list, outputting a revaluation for // each price difference. foreach (const date_map::value_type& price, pricing_dates) { output_revaluation(post, price.first); last_total = repriced_total; } break; } default: assert(false); break; } } void changed_value_posts::operator()(post_t& post) { if (last_post) { if (! for_accounts_report && ! historical_prices_only) output_intermediate_prices(*last_post, post.value_date()); output_revaluation(*last_post, post.value_date()); } if (changed_values_only) post.xdata().add_flags(POST_EXT_DISPLAYED); item_handler::operator()(post); bind_scope_t bound_scope(report, post); last_total = total_expr.calc(bound_scope); last_post = &post; } void subtotal_posts::report_subtotal(const char * spec_fmt, const optional& interval) { if (component_posts.empty()) return; optional range_start = interval ? interval->start : none; optional range_finish = interval ? interval->inclusive_end() : none; if (! range_start || ! range_finish) { foreach (post_t * post, component_posts) { date_t date = post->date(); date_t value_date = post->value_date(); #if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif if (! range_start || date < *range_start) range_start = date; if (! range_finish || value_date > *range_finish) range_finish = value_date; #if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7 #pragma GCC diagnostic pop #endif } } component_posts.clear(); std::ostringstream out_date; if (spec_fmt) { out_date << format_date(*range_finish, FMT_CUSTOM, spec_fmt); } else if (date_format) { out_date << "- " << format_date(*range_finish, FMT_CUSTOM, date_format->c_str()); } else { out_date << "- " << format_date(*range_finish); } xact_t& xact = temps.create_xact(); xact.payee = out_date.str(); xact._date = *range_start; foreach (values_map::value_type& pair, values) handle_value(/* value= */ pair.second.value, /* account= */ pair.second.account, /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ *range_finish, /* act_date_p= */ false); values.clear(); } void subtotal_posts::operator()(post_t& post) { component_posts.push_back(&post); account_t * acct = post.reported_account(); assert(acct); #if 0 // jww (2012-04-06): The problem with doing this early is that // fn_display_amount will recalculate this again. For example, if you // use --invert, it will invert both here and in the display amount, // effectively negating it. bind_scope_t bound_scope(*amount_expr.get_context(), post); value_t amount(amount_expr.calc(bound_scope)); #else value_t amount(post.amount); #endif post.xdata().compound_value = amount; post.xdata().add_flags(POST_EXT_COMPOUND); values_map::iterator i = values.find(acct->fullname()); if (i == values.end()) { #if DEBUG_ON std::pair result = #endif values.insert(values_pair (acct->fullname(), acct_value_t(acct, amount, post.has_flags(POST_VIRTUAL), post.has_flags(POST_MUST_BALANCE)))); #if DEBUG_ON assert(result.second); #endif } else { if (post.has_flags(POST_VIRTUAL) != (*i).second.is_virtual) throw_(std::logic_error, _("'equity' cannot accept virtual and " "non-virtual postings to the same account")); add_or_set_value((*i).second.value, amount); } // If the account for this post is all virtual, mark it as // such, so that `handle_value' can show "(Account)" for accounts // that contain only virtual posts. post.reported_account()->xdata().add_flags(ACCOUNT_EXT_AUTO_VIRTUALIZE); if (! post.has_flags(POST_VIRTUAL)) post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_NON_VIRTUALS); else if (! post.has_flags(POST_MUST_BALANCE)) post.reported_account()->xdata().add_flags(ACCOUNT_EXT_HAS_UNB_VIRTUALS); } void interval_posts::report_subtotal(const date_interval_t& ival) { if (exact_periods) subtotal_posts::report_subtotal(); else subtotal_posts::report_subtotal(NULL, ival); } namespace { struct sort_posts_by_date { bool operator()(post_t * left, post_t * right) const { return left->date() < right->date(); } }; } void interval_posts::operator()(post_t& post) { // If there is a duration (such as weekly), we must generate the // report in two passes. Otherwise, we only have to check whether the // post falls within the reporting period. if (interval.duration) { all_posts.push_back(&post); } else if (interval.find_period(post.date())) { item_handler::operator()(post); } } void interval_posts::flush() { if (! interval.duration) { item_handler::flush(); return; } // Sort all the postings we saw by date ascending std::stable_sort(all_posts.begin(), all_posts.end(), sort_posts_by_date()); // Determine the beginning interval by using the earliest post if (all_posts.size() > 0 && 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 // before moving on, until we reach the end of all_posts bool saw_posts = false; for (std::deque::iterator i = all_posts.begin(); i != all_posts.end(); ) { post_t * post(*i); DEBUG("filters.interval", "Considering post " << post->date() << " = " << post->amount); #if DEBUG_ON DEBUG("filters.interval", "interval is:"); debug_interval(interval); #endif assert(! interval.finish || post->date() < *interval.finish); if (interval.within_period(post->date())) { DEBUG("filters.interval", "Calling subtotal_posts::operator()"); subtotal_posts::operator()(*post); ++i; saw_posts = true; } else { if (saw_posts) { DEBUG("filters.interval", "Calling subtotal_posts::report_subtotal()"); report_subtotal(interval); saw_posts = false; } else if (generate_empty_posts) { // Generate a null posting, so the intervening periods can be // seen when -E is used, or if the calculated amount ends up // being non-zero xact_t& null_xact = temps.create_xact(); null_xact._date = interval.inclusive_end(); post_t& null_post = temps.create_post(null_xact, empty_account); null_post.add_flags(POST_CALCULATED); null_post.amount = 0L; subtotal_posts::operator()(null_post); report_subtotal(interval); } DEBUG("filters.interval", "Advancing interval"); ++interval; } } // If the last postings weren't reported, do so now. if (saw_posts) { DEBUG("filters.interval", "Calling subtotal_posts::report_subtotal() at end"); report_subtotal(interval); } // Tell our parent class to flush subtotal_posts::flush(); } namespace { struct create_post_from_amount { post_handler_ptr handler; xact_t& xact; account_t& balance_account; temporaries_t& temps; explicit create_post_from_amount(post_handler_ptr _handler, xact_t& _xact, account_t& _balance_account, temporaries_t& _temps) : handler(_handler), xact(_xact), balance_account(_balance_account), temps(_temps) { TRACE_CTOR(create_post_from_amount, "post_handler_ptr, xact_t&, account_t&, temporaries_t&"); } create_post_from_amount(const create_post_from_amount& other) : handler(other.handler), xact(other.xact), balance_account(other.balance_account), temps(other.temps) { TRACE_CTOR(create_post_from_amount, "copy"); } ~create_post_from_amount() throw() { TRACE_DTOR(create_post_from_amount); } void operator()(const amount_t& amount) { post_t& balance_post = temps.create_post(xact, &balance_account); balance_post.amount = - amount; (*handler)(balance_post); } }; } void posts_as_equity::report_subtotal() { date_t finish; foreach (post_t * post, component_posts) { date_t date = post->date(); if (! is_valid(finish) || date > finish) finish = date; } component_posts.clear(); xact_t& xact = temps.create_xact(); xact.payee = _("Opening Balances"); xact._date = finish; value_t total = 0L; foreach (values_map::value_type& pair, values) { value_t value(pair.second.value.strip_annotations(report.what_to_keep())); if (! value.is_zero()) { if (value.is_balance()) { foreach (const balance_t::amounts_map::value_type& amount_pair, value.as_balance_lval().amounts) { if (! amount_pair.second.is_zero()) handle_value(/* value= */ amount_pair.second, /* account= */ pair.second.account, /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ finish, /* act_date_p= */ false); } } else { handle_value(/* value= */ value.to_amount(), /* account= */ pair.second.account, /* xact= */ &xact, /* temps= */ temps, /* handler= */ handler, /* date= */ finish, /* act_date_p= */ false); } } if (! pair.second.is_virtual || pair.second.must_balance) total += value; } values.clear(); // This last part isn't really needed, since an Equity:Opening // Balances posting with a null amount will automatically balance with // all the other postings generated. But it does make the full // balancing amount clearer to the user. if (! total.is_zero()) { create_post_from_amount post_creator(handler, xact, *balance_account, temps); if (total.is_balance()) total.as_balance_lval().map_sorted_amounts(post_creator); else post_creator(total.to_amount()); } } void by_payee_posts::flush() { foreach (payee_subtotals_map::value_type& pair, payee_subtotals) pair.second->report_subtotal(pair.first.c_str()); item_handler::flush(); payee_subtotals.clear(); } void by_payee_posts::operator()(post_t& post) { payee_subtotals_map::iterator i = payee_subtotals.find(post.payee()); if (i == payee_subtotals.end()) { payee_subtotals_pair temp(post.payee(), shared_ptr(new subtotal_posts(handler, amount_expr))); std::pair result = payee_subtotals.insert(temp); assert(result.second); if (! result.second) return; i = result.first; } (*(*i).second)(post); } void transfer_details::operator()(post_t& post) { xact_t& xact = temps.copy_xact(*post.xact); xact._date = post.date(); post_t& temp = temps.copy_post(post, xact); temp.set_state(post.state()); bind_scope_t bound_scope(scope, temp); value_t substitute(expr.calc(bound_scope)); if (! substitute.is_null()) { switch (which_element) { case SET_DATE: temp._date = substitute.to_date(); break; case SET_ACCOUNT: { string account_name = substitute.to_string(); if (! account_name.empty() && account_name[account_name.length() - 1] != ':') { account_t * prev_account = temp.account; temp.account->remove_post(&temp); account_name += ':'; account_name += prev_account->fullname(); std::list account_names; split_string(account_name, ':', account_names); temp.account = create_temp_account_from_path(account_names, temps, xact.journal->master); temp.account->add_post(&temp); temp.account->add_flags(prev_account->flags()); if (prev_account->has_xdata()) temp.account->xdata().add_flags(prev_account->xdata().flags()); } break; } case SET_PAYEE: xact.payee = substitute.to_string(); break; } } item_handler::operator()(temp); } void day_of_week_posts::flush() { for (int i = 0; i < 7; i++) { foreach (post_t * post, days_of_the_week[i]) subtotal_posts::operator()(*post); subtotal_posts::report_subtotal("%As"); days_of_the_week[i].clear(); } subtotal_posts::flush(); } void generate_posts::add_period_xacts(period_xacts_list& period_xacts) { foreach (period_xact_t * xact, period_xacts) foreach (post_t * post, xact->posts) add_post(xact->period, *post); } void generate_posts::add_post(const date_interval_t& period, post_t& post) { pending_posts.push_back(pending_posts_pair(period, &post)); } void budget_posts::report_budget_items(const date_t& date) { if (pending_posts.size() == 0) return; bool reported; do { std::list posts_to_erase; reported = false; for (pending_posts_list::iterator i = pending_posts.begin(); i != pending_posts.end(); i++) { pending_posts_list::value_type& pair(*i); optional begin = pair.first.start; if (! begin) { optional range_begin; if (pair.first.range) range_begin = pair.first.range->begin(); DEBUG("budget.generate", "Finding period for pending post"); if (! pair.first.find_period(range_begin ? *range_begin : date)) continue; if (! pair.first.start) throw_(std::logic_error, _("Failed to find period for periodic transaction")); begin = pair.first.start; } #if DEBUG_ON DEBUG("budget.generate", "begin = " << *begin); DEBUG("budget.generate", "date = " << date); if (pair.first.finish) DEBUG("budget.generate", "pair.first.finish = " << *pair.first.finish); #endif if (*begin <= date && (! pair.first.finish || *begin < *pair.first.finish)) { post_t& post = *pair.second; ++pair.first; if (! pair.first.start) posts_to_erase.push_back(i); DEBUG("budget.generate", "Reporting budget for " << post.reported_account()->fullname()); xact_t& xact = temps.create_xact(); xact.payee = _("Budget transaction"); xact._date = begin; post_t& temp = temps.copy_post(post, xact); temp.amount.in_place_negate(); if (flags & BUDGET_WRAP_VALUES) { value_t seq; seq.push_back(0L); seq.push_back(temp.amount); temp.xdata().compound_value = seq; temp.xdata().add_flags(POST_EXT_COMPOUND); } item_handler::operator()(temp); reported = true; } } foreach (pending_posts_list::iterator& i, posts_to_erase) pending_posts.erase(i); } while (reported); } void budget_posts::operator()(post_t& post) { bool post_in_budget = false; foreach (pending_posts_list::value_type& pair, pending_posts) { for (account_t * acct = post.reported_account(); acct; acct = acct->parent) { if (acct == (*pair.second).reported_account()) { post_in_budget = true; // Report the post as if it had occurred in the parent account. if (post.reported_account() != acct) post.set_reported_account(acct); goto handle; } } } handle: if (post_in_budget && flags & BUDGET_BUDGETED) { report_budget_items(post.date()); item_handler::operator()(post); } else if (! post_in_budget && flags & BUDGET_UNBUDGETED) { item_handler::operator()(post); } } void budget_posts::flush() { if (flags & BUDGET_BUDGETED) report_budget_items(terminus); item_handler::flush(); } void forecast_posts::add_post(const date_interval_t& period, post_t& post) { date_interval_t i(period); if (! i.start && ! i.find_period(CURRENT_DATE())) return; generate_posts::add_post(i, post); // Advance the period's interval until it is at or beyond the current // date. while (*i.start < CURRENT_DATE()) ++i; } void forecast_posts::flush() { posts_list passed; date_t last = CURRENT_DATE(); // If there are period transactions to apply in a continuing series until // the forecast condition is met, generate those transactions now. Note // that no matter what, we abandon forecasting beyond the next 5 years. // // It works like this: // // Earlier, in forecast_posts::add_period_xacts, we cut up all the periodic // transactions into their components postings, so that we have N "periodic // postings". For example, if the user had this: // // ~ daily // Expenses:Food $10 // Expenses:Auto:Gas $20 // ~ monthly // Expenses:Food $100 // Expenses:Auto:Gas $200 // // We now have 4 periodic postings in `pending_posts'. // // Each periodic postings gets its own copy of its parent transaction's // period, which is modified as we go. This is found in the second member // of the pending_posts_list for each posting. // // The algorithm below works by iterating through the N periodic postings // over and over, until each of them mets the termination critera for the // forecast and is removed from the set. while (pending_posts.size() > 0) { // At each step through the loop, we find the first periodic posting whose // period contains the earliest starting date. pending_posts_list::iterator least = pending_posts.begin(); for (pending_posts_list::iterator i = ++pending_posts.begin(); i != pending_posts.end(); i++) { assert((*i).first.start); assert((*least).first.start); if (*(*i).first.start < *(*least).first.start) least = i; } #if !NO_ASSERTS if ((*least).first.finish) assert(*(*least).first.start < *(*least).first.finish); #endif // If the next date in the series for this periodic posting is more than 5 // years beyond the last valid post we generated, drop it from further // consideration. date_t& next(*(*least).first.next); assert(next > *(*least).first.start); if (static_cast((next - last).days()) > static_cast(365U) * forecast_years) { DEBUG("filters.forecast", "Forecast transaction exceeds " << forecast_years << " years beyond today"); pending_posts.erase(least); continue; } // `post' refers to the posting defined in the period transaction. We // make a copy of it within a temporary transaction with the payee // "Forecast transaction". post_t& post = *(*least).second; xact_t& xact = temps.create_xact(); xact.payee = _("Forecast transaction"); xact._date = next; post_t& temp = temps.copy_post(post, xact); // Submit the generated posting DEBUG("filters.forecast", "Forecast transaction: " << temp.date() << " " << temp.account->fullname() << " " << temp.amount); item_handler::operator()(temp); // If the generated posting matches the user's report query, check whether // it also fails to match the continuation condition for the forecast. If // it does, drop this periodic posting from consideration. if (temp.has_xdata() && temp.xdata().has_flags(POST_EXT_MATCHES)) { DEBUG("filters.forecast", " matches report query"); bind_scope_t bound_scope(context, temp); if (! pred(bound_scope)) { DEBUG("filters.forecast", " fails to match continuation criteria"); pending_posts.erase(least); continue; } } // Increment the 'least', but remove it from pending_posts if it // exceeds its own boundaries. ++(*least).first; if (! (*least).first.start) { pending_posts.erase(least); continue; } } item_handler::flush(); } inject_posts::inject_posts(post_handler_ptr handler, const string& tag_list, account_t * master) : item_handler(handler) { scoped_array buf(new char[tag_list.length() + 1]); std::strcpy(buf.get(), tag_list.c_str()); for (char * q = std::strtok(buf.get(), ","); q; q = std::strtok(NULL, ",")) { std::list account_names; split_string(q, ':', account_names); account_t * account = create_temp_account_from_path(account_names, temps, master); account->add_flags(ACCOUNT_GENERATED); tags_list.push_back (tags_list_pair(q, tag_mapping_pair(account, tag_injected_set()))); } TRACE_CTOR(inject_posts, "post_handler_ptr, string, account_t *"); } void inject_posts::operator()(post_t& post) { foreach (tags_list_pair& pair, tags_list) { optional tag_value = post.get_tag(pair.first, false); // When checking if the transaction has the tag, only inject once // per transaction. if (! tag_value && pair.second.second.find(post.xact) == pair.second.second.end() && (tag_value = post.xact->get_tag(pair.first))) pair.second.second.insert(post.xact); if (tag_value) { xact_t& xact = temps.copy_xact(*post.xact); xact._date = post.date(); xact.add_flags(ITEM_GENERATED); post_t& temp = temps.copy_post(post, xact); temp.account = pair.second.first; temp.amount = tag_value->to_amount(); temp.add_flags(ITEM_GENERATED); item_handler::operator()(temp); } } item_handler::operator()(post); } } // namespace ledger ledger-3.1.1+dfsg1/src/filters.h000066400000000000000000000647021266023441000163750ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup report */ /** * @file filters.h * @author John Wiegley * * @ingroup report */ #ifndef _FILTERS_H #define _FILTERS_H #include "chain.h" #include "xact.h" #include "post.h" #include "account.h" #include "temps.h" namespace ledger { ////////////////////////////////////////////////////////////////////// // // Posting collector // class post_splitter : public item_handler { public: typedef std::map value_to_posts_map; typedef function custom_flusher_t; protected: value_to_posts_map posts_map; post_handler_ptr post_chain; report_t& report; expr_t& group_by_expr; custom_flusher_t preflush_func; optional postflush_func; public: post_splitter(post_handler_ptr _post_chain, report_t& _report, expr_t& _group_by_expr) : post_chain(_post_chain), report(_report), group_by_expr(_group_by_expr) { preflush_func = bind(&post_splitter::print_title, this, _1); TRACE_CTOR(post_splitter, "scope_t&, post_handler_ptr, expr_t"); } virtual ~post_splitter() { TRACE_DTOR(post_splitter); } void set_preflush_func(custom_flusher_t functor) { preflush_func = functor; } void set_postflush_func(custom_flusher_t functor) { postflush_func = functor; } virtual void print_title(const value_t& val); virtual void flush(); virtual void operator()(post_t& post); virtual void clear() { posts_map.clear(); post_chain->clear(); item_handler::clear(); } }; ////////////////////////////////////////////////////////////////////// // // Posting filters // class ignore_posts : public item_handler { public: virtual void operator()(post_t&) {} }; class collect_posts : public item_handler { public: std::vector posts; collect_posts() : item_handler() { TRACE_CTOR(collect_posts, ""); } virtual ~collect_posts() { TRACE_DTOR(collect_posts); } std::size_t length() const { return posts.size(); } std::vector::iterator begin() { return posts.begin(); } std::vector::iterator end() { return posts.end(); } virtual void flush() {} virtual void operator()(post_t& post) { posts.push_back(&post); } virtual void clear() { posts.clear(); item_handler::clear(); } }; template class pass_down_posts : public item_handler { pass_down_posts(); public: pass_down_posts(post_handler_ptr handler, Iterator& iter) : item_handler(handler) { while (post_t * post = *iter) { try { item_handler::operator()(*post); } catch (const std::exception&) { add_error_context(item_context(*post, _("While handling posting"))); throw; } iter.increment(); } item_handler::flush(); TRACE_CTOR(pass_down_posts, "post_handler_ptr, posts_iterator"); } virtual ~pass_down_posts() { TRACE_DTOR(pass_down_posts); } }; class push_to_posts_list : public item_handler { push_to_posts_list(); public: posts_list& posts; push_to_posts_list(posts_list& _posts) : posts(_posts) { TRACE_CTOR(push_to_posts_list, "posts_list&"); } virtual ~push_to_posts_list() { TRACE_DTOR(push_to_posts_list); } virtual void operator()(post_t& post) { posts.push_back(&post); } }; class truncate_xacts : public item_handler { int head_count; int tail_count; bool completed; posts_list posts; std::size_t xacts_seen; xact_t * last_xact; truncate_xacts(); public: truncate_xacts(post_handler_ptr handler, int _head_count, int _tail_count) : item_handler(handler), head_count(_head_count), tail_count(_tail_count), completed(false), xacts_seen(0), last_xact(NULL) { TRACE_CTOR(truncate_xacts, "post_handler_ptr, int, int"); } virtual ~truncate_xacts() { TRACE_DTOR(truncate_xacts); } virtual void flush(); virtual void operator()(post_t& post); virtual void clear() { completed = false; posts.clear(); xacts_seen = 0; last_xact = NULL; item_handler::clear(); } }; class sort_posts : public item_handler { typedef std::deque posts_deque; posts_deque posts; expr_t sort_order; sort_posts(); public: sort_posts(post_handler_ptr handler, const expr_t& _sort_order) : item_handler(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, "post_handler_ptr, const value_expr&"); } sort_posts(post_handler_ptr handler, const string& _sort_order) : item_handler(handler), sort_order(_sort_order) { TRACE_CTOR(sort_posts, "post_handler_ptr, const string&"); } virtual ~sort_posts() { TRACE_DTOR(sort_posts); } virtual void post_accumulated_posts(); virtual void flush() { post_accumulated_posts(); item_handler::flush(); } virtual void operator()(post_t& post) { posts.push_back(&post); } virtual void clear() { posts.clear(); sort_order.mark_uncompiled(); item_handler::clear(); } }; class sort_xacts : public item_handler { sort_posts sorter; xact_t * last_xact; sort_xacts(); public: sort_xacts(post_handler_ptr handler, const expr_t& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const value_expr&"); } sort_xacts(post_handler_ptr handler, const string& _sort_order) : sorter(handler, _sort_order) { TRACE_CTOR(sort_xacts, "post_handler_ptr, const string&"); } virtual ~sort_xacts() { TRACE_DTOR(sort_xacts); } virtual void flush() { sorter.flush(); item_handler::flush(); } virtual void operator()(post_t& post) { if (last_xact && post.xact != last_xact) sorter.post_accumulated_posts(); sorter(post); last_xact = post.xact; } virtual void clear() { sorter.clear(); last_xact = NULL; item_handler::clear(); } }; class filter_posts : public item_handler { predicate_t pred; scope_t& context; filter_posts(); public: filter_posts(post_handler_ptr handler, const predicate_t& predicate, scope_t& _context) : item_handler(handler), pred(predicate), context(_context) { TRACE_CTOR(filter_posts, "post_handler_ptr, predicate_t, scope_t&"); } virtual ~filter_posts() { TRACE_DTOR(filter_posts); } virtual void operator()(post_t& post) { bind_scope_t bound_scope(context, post); if (pred(bound_scope)) { post.xdata().add_flags(POST_EXT_MATCHES); (*handler)(post); } } virtual void clear() { pred.mark_uncompiled(); item_handler::clear(); } }; class anonymize_posts : public item_handler { typedef std::map commodity_index_map; typedef variate_generator > int_generator_t; temporaries_t temps; commodity_index_map comms; std::size_t next_comm_id; xact_t * last_xact; mt19937 rnd_gen; uniform_int<> integer_range; int_generator_t integer_gen; anonymize_posts(); public: anonymize_posts(post_handler_ptr handler) : item_handler(handler), next_comm_id(0), last_xact(NULL), rnd_gen(static_cast(static_cast(std::time(0)))), integer_range(1, 2000000000L), integer_gen(rnd_gen, integer_range) { TRACE_CTOR(anonymize_posts, "post_handler_ptr"); } virtual ~anonymize_posts() { TRACE_DTOR(anonymize_posts); handler.reset(); } void render_commodity(amount_t& amt); virtual void operator()(post_t& post); virtual void clear() { temps.clear(); comms.clear(); last_xact = NULL; item_handler::clear(); } }; class calc_posts : public item_handler { post_t * last_post; expr_t& amount_expr; bool calc_running_total; calc_posts(); public: calc_posts(post_handler_ptr handler, expr_t& _amount_expr, bool _calc_running_total = false) : item_handler(handler), last_post(NULL), amount_expr(_amount_expr), calc_running_total(_calc_running_total) { TRACE_CTOR(calc_posts, "post_handler_ptr, expr_t&, bool"); } virtual ~calc_posts() { TRACE_DTOR(calc_posts); } virtual void operator()(post_t& post); virtual void clear() { last_post = NULL; amount_expr.mark_uncompiled(); item_handler::clear(); } }; class collapse_posts : public item_handler { expr_t& amount_expr; predicate_t display_predicate; predicate_t only_predicate; value_t subtotal; std::size_t count; xact_t * last_xact; post_t * last_post; temporaries_t temps; account_t * totals_account; bool only_collapse_if_zero; std::list component_posts; report_t& report; collapse_posts(); public: collapse_posts(post_handler_ptr handler, report_t& _report, expr_t& _amount_expr, predicate_t _display_predicate, predicate_t _only_predicate, bool _only_collapse_if_zero = false) : item_handler(handler), amount_expr(_amount_expr), display_predicate(_display_predicate), only_predicate(_only_predicate), count(0), last_xact(NULL), last_post(NULL), only_collapse_if_zero(_only_collapse_if_zero), report(_report) { create_accounts(); TRACE_CTOR(collapse_posts, "post_handler_ptr, ..."); } virtual ~collapse_posts() { TRACE_DTOR(collapse_posts); handler.reset(); } void create_accounts() { totals_account = &temps.create_account(_("")); } virtual void flush() { report_subtotal(); item_handler::flush(); } void report_subtotal(); virtual void operator()(post_t& post); virtual void clear() { amount_expr.mark_uncompiled(); display_predicate.mark_uncompiled(); only_predicate.mark_uncompiled(); subtotal = value_t(); count = 0; last_xact = NULL; last_post = NULL; temps.clear(); create_accounts(); component_posts.clear(); item_handler::clear(); } }; class related_posts : public item_handler { posts_list posts; bool also_matching; related_posts(); public: related_posts(post_handler_ptr handler, const bool _also_matching = false) : item_handler(handler), also_matching(_also_matching) { TRACE_CTOR(related_posts, "post_handler_ptr, const bool"); } virtual ~related_posts() throw() { TRACE_DTOR(related_posts); } virtual void flush(); virtual void operator()(post_t& post) { post.xdata().add_flags(POST_EXT_RECEIVED); posts.push_back(&post); } virtual void clear() { posts.clear(); item_handler::clear(); } }; class display_filter_posts : public item_handler { // This filter requires that calc_posts be used at some point // later in the chain. report_t& report; expr_t& display_amount_expr; expr_t& display_total_expr; bool show_rounding; value_t last_display_total; temporaries_t temps; account_t * rounding_account; display_filter_posts(); public: account_t * revalued_account; display_filter_posts(post_handler_ptr handler, report_t& _report, bool _show_rounding); virtual ~display_filter_posts() { TRACE_DTOR(display_filter_posts); handler.reset(); } void create_accounts() { rounding_account = &temps.create_account(_("")); revalued_account = &temps.create_account(_("")); } bool output_rounding(post_t& post); virtual void operator()(post_t& post); virtual void clear() { display_amount_expr.mark_uncompiled(); display_total_expr.mark_uncompiled(); last_display_total = value_t(); temps.clear(); create_accounts(); item_handler::clear(); } }; class changed_value_posts : public item_handler { // This filter requires that calc_posts be used at some point // later in the chain. report_t& report; expr_t& total_expr; expr_t& display_total_expr; bool changed_values_only; bool historical_prices_only; bool for_accounts_report; bool show_unrealized; post_t * last_post; value_t last_total; value_t repriced_total; temporaries_t temps; account_t * revalued_account; account_t * gains_equity_account; account_t * losses_equity_account; display_filter_posts * display_filter; changed_value_posts(); public: changed_value_posts(post_handler_ptr handler, report_t& _report, bool _for_accounts_report, bool _show_unrealized, display_filter_posts * _display_filter); virtual ~changed_value_posts() { TRACE_DTOR(changed_value_posts); handler.reset(); } void create_accounts() { revalued_account = (display_filter ? display_filter->revalued_account : &temps.create_account(_(""))); } virtual void flush(); void output_revaluation(post_t& post, const date_t& current); void output_intermediate_prices(post_t& post, const date_t& current); virtual void operator()(post_t& post); virtual void clear() { total_expr.mark_uncompiled(); display_total_expr.mark_uncompiled(); last_post = NULL; last_total = value_t(); temps.clear(); create_accounts(); item_handler::clear(); } }; class subtotal_posts : public item_handler { subtotal_posts(); protected: class acct_value_t { acct_value_t(); public: account_t * account; value_t value; bool is_virtual; bool must_balance; acct_value_t(account_t * a, bool _is_virtual = false, bool _must_balance = false) : account(a), is_virtual(_is_virtual), must_balance(_must_balance) { TRACE_CTOR(acct_value_t, "account_t *, bool, bool"); } acct_value_t(account_t * a, value_t& v, bool _is_virtual = false, bool _must_balance = false) : account(a), value(v), is_virtual(_is_virtual), must_balance(_must_balance) { TRACE_CTOR(acct_value_t, "account_t *, value_t&, bool, bool"); } acct_value_t(const acct_value_t& av) : account(av.account), value(av.value), is_virtual(av.is_virtual), must_balance(av.must_balance) { TRACE_CTOR(acct_value_t, "copy"); } ~acct_value_t() throw() { TRACE_DTOR(acct_value_t); } }; typedef std::map values_map; typedef std::pair values_pair; protected: expr_t& amount_expr; values_map values; optional date_format; temporaries_t temps; std::deque component_posts; public: subtotal_posts(post_handler_ptr handler, expr_t& _amount_expr, const optional& _date_format = none) : item_handler(handler), amount_expr(_amount_expr), date_format(_date_format) { TRACE_CTOR(subtotal_posts, "post_handler_ptr, expr_t&, const optional&"); } virtual ~subtotal_posts() { TRACE_DTOR(subtotal_posts); handler.reset(); } void report_subtotal(const char * spec_fmt = NULL, const optional& interval = none); virtual void flush() { if (values.size() > 0) report_subtotal(); item_handler::flush(); } virtual void operator()(post_t& post); virtual void clear() { amount_expr.mark_uncompiled(); values.clear(); temps.clear(); component_posts.clear(); item_handler::clear(); } }; class interval_posts : public subtotal_posts { date_interval_t start_interval; date_interval_t interval; account_t * empty_account; bool exact_periods; bool generate_empty_posts; std::deque all_posts; interval_posts(); public: interval_posts(post_handler_ptr _handler, expr_t& amount_expr, const date_interval_t& _interval, bool _exact_periods = false, bool _generate_empty_posts = false) : subtotal_posts(_handler, amount_expr), start_interval(_interval), interval(start_interval), exact_periods(_exact_periods), generate_empty_posts(_generate_empty_posts) { create_accounts(); TRACE_CTOR(interval_posts, "post_handler_ptr, expr_t&, date_interval_t, bool, bool"); } virtual ~interval_posts() throw() { TRACE_DTOR(interval_posts); } void create_accounts() { empty_account = &temps.create_account(_("")); } void report_subtotal(const date_interval_t& ival); #if DEBUG_ON void debug_interval(const date_interval_t& ival) { if (ival.start) DEBUG("filters.interval", "start = " << *ival.start); else DEBUG("filters.interval", "no start"); if (ival.finish) DEBUG("filters.interval", "finish = " << *ival.finish); else DEBUG("filters.interval", "no finish"); } #endif virtual void operator()(post_t& post); virtual void flush(); virtual void clear() { interval = start_interval; subtotal_posts::clear(); create_accounts(); } }; class posts_as_equity : public subtotal_posts { report_t& report; post_t * last_post; account_t * equity_account; account_t * balance_account; posts_as_equity(); public: posts_as_equity(post_handler_ptr _handler, report_t& _report, expr_t& amount_expr) : subtotal_posts(_handler, amount_expr), report(_report) { create_accounts(); TRACE_CTOR(posts_as_equity, "post_handler_ptr, expr_t&"); } virtual ~posts_as_equity() throw() { TRACE_DTOR(posts_as_equity); } void create_accounts() { equity_account = &temps.create_account(_("Equity")); balance_account = equity_account->find_account(_("Opening Balances")); } void report_subtotal(); virtual void flush() { report_subtotal(); subtotal_posts::flush(); } virtual void clear() { last_post = NULL; subtotal_posts::clear(); create_accounts(); } }; class by_payee_posts : public item_handler { typedef std::map > payee_subtotals_map; typedef std::pair > payee_subtotals_pair; expr_t& amount_expr; payee_subtotals_map payee_subtotals; by_payee_posts(); public: by_payee_posts(post_handler_ptr handler, expr_t& _amount_expr) : item_handler(handler), amount_expr(_amount_expr) { TRACE_CTOR(by_payee_posts, "post_handler_ptr, expr_t&"); } virtual ~by_payee_posts() { TRACE_DTOR(by_payee_posts); } virtual void flush(); virtual void operator()(post_t& post); virtual void clear() { amount_expr.mark_uncompiled(); payee_subtotals.clear(); item_handler::clear(); } }; class transfer_details : public item_handler { account_t * master; expr_t expr; scope_t& scope; temporaries_t temps; transfer_details(); public: enum element_t { SET_DATE, SET_ACCOUNT, SET_PAYEE } which_element; transfer_details(post_handler_ptr handler, element_t _which_element, account_t * _master, const expr_t& _expr, scope_t& _scope) : item_handler(handler), master(_master), expr(_expr), scope(_scope), which_element(_which_element) { TRACE_CTOR(transfer_details, "post_handler_ptr, element_t, account_t *, expr_t, scope_t&"); } virtual ~transfer_details() { TRACE_DTOR(transfer_details); handler.reset(); } virtual void operator()(post_t& post); virtual void clear() { expr.mark_uncompiled(); temps.clear(); item_handler::clear(); } }; class day_of_week_posts : public subtotal_posts { posts_list days_of_the_week[7]; day_of_week_posts(); public: day_of_week_posts(post_handler_ptr handler, expr_t& amount_expr) : subtotal_posts(handler, amount_expr) { TRACE_CTOR(day_of_week_posts, "post_handler_ptr, bool"); } virtual ~day_of_week_posts() throw() { TRACE_DTOR(day_of_week_posts); } virtual void flush(); virtual void operator()(post_t& post) { days_of_the_week[post.date().day_of_week()].push_back(&post); } virtual void clear() { for (int i = 0; i < 7; i++) days_of_the_week[i].clear(); subtotal_posts::clear(); } }; class generate_posts : public item_handler { generate_posts(); protected: typedef std::pair pending_posts_pair; typedef std::list pending_posts_list; pending_posts_list pending_posts; temporaries_t temps; public: generate_posts(post_handler_ptr handler) : item_handler(handler) { TRACE_CTOR(generate_posts, "post_handler_ptr"); } virtual ~generate_posts() { TRACE_DTOR(generate_posts); handler.reset(); } void add_period_xacts(period_xacts_list& period_xacts); virtual void add_post(const date_interval_t& period, post_t& post); virtual void clear() { pending_posts.clear(); temps.clear(); item_handler::clear(); } }; class budget_posts : public generate_posts { #define BUDGET_NO_BUDGET 0x00 #define BUDGET_BUDGETED 0x01 #define BUDGET_UNBUDGETED 0x02 #define BUDGET_WRAP_VALUES 0x04 uint_least8_t flags; date_t terminus; budget_posts(); public: budget_posts(post_handler_ptr handler, date_t _terminus, uint_least8_t _flags = BUDGET_BUDGETED) : generate_posts(handler), flags(_flags), terminus(_terminus) { TRACE_CTOR(budget_posts, "post_handler_ptr, date_t, uint_least8_t"); } virtual ~budget_posts() throw() { TRACE_DTOR(budget_posts); } void report_budget_items(const date_t& date); virtual void flush(); virtual void operator()(post_t& post); }; class forecast_posts : public generate_posts { predicate_t pred; scope_t& context; const std::size_t forecast_years; public: forecast_posts(post_handler_ptr handler, const predicate_t& predicate, scope_t& _context, const std::size_t _forecast_years) : generate_posts(handler), pred(predicate), context(_context), forecast_years(_forecast_years) { TRACE_CTOR(forecast_posts, "post_handler_ptr, predicate_t, scope_t&, std::size_t"); } virtual ~forecast_posts() throw() { TRACE_DTOR(forecast_posts); } virtual void add_post(const date_interval_t& period, post_t& post); virtual void flush(); virtual void clear() { pred.mark_uncompiled(); generate_posts::clear(); } }; class inject_posts : public item_handler { typedef std::set tag_injected_set; typedef std::pair tag_mapping_pair; typedef std::pair tags_list_pair; std::list tags_list; temporaries_t temps; public: inject_posts(post_handler_ptr handler, const string& tag_list, account_t * master); virtual ~inject_posts() throw() { TRACE_DTOR(inject_posts); handler.reset(); } virtual void operator()(post_t& post); }; ////////////////////////////////////////////////////////////////////// // // Account filters // template class pass_down_accounts : public item_handler { pass_down_accounts(); optional pred; optional context; public: pass_down_accounts(acct_handler_ptr handler, Iterator& iter, const optional& _pred = none, const optional& _context = none) : item_handler(handler), pred(_pred), context(_context) { TRACE_CTOR(pass_down_accounts, "acct_handler_ptr, accounts_iterator, ..."); while (account_t * account = *iter++) { if (! pred) { item_handler::operator()(*account); } else { bind_scope_t bound_scope(*context, *account); if ((*pred)(bound_scope)) item_handler::operator()(*account); } } item_handler::flush(); } virtual ~pass_down_accounts() { TRACE_DTOR(pass_down_accounts); } virtual void clear() { if (pred) pred->mark_uncompiled(); item_handler::clear(); } }; } // namespace ledger #endif // _FILTERS_H ledger-3.1.1+dfsg1/src/flags.h000066400000000000000000000114731266023441000160160ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup util */ /** * @file flags.h * @author John Wiegley * * @ingroup util */ #ifndef _FLAGS_H #define _FLAGS_H template class supports_flags { public: typedef T flags_t; protected: flags_t _flags; public: supports_flags() : _flags(static_cast(0)) { TRACE_CTOR(supports_flags, ""); } supports_flags(const supports_flags& arg) : _flags(arg._flags) { TRACE_CTOR(supports_flags, "copy"); } supports_flags(const flags_t& arg) : _flags(arg) { TRACE_CTOR(supports_flags, "const flags_t&"); } ~supports_flags() throw() { TRACE_DTOR(supports_flags); } supports_flags& operator=(const supports_flags& other) { _flags = other._flags; return *this; } flags_t flags() const { return _flags; } bool has_flags(const flags_t arg) const { return _flags & arg; } void set_flags(const flags_t arg) { _flags = arg; } void clear_flags() { _flags = static_cast(0); } void add_flags(const flags_t arg) { _flags = static_cast(static_cast(_flags) | static_cast(arg)); } void drop_flags(const flags_t arg) { _flags = static_cast(static_cast(_flags) & static_cast(~arg)); } }; template class basic_flags_t : public supports_flags { public: basic_flags_t() { TRACE_CTOR(basic_flags_t, ""); } basic_flags_t(const T& bits) { TRACE_CTOR(basic_flags_t, "const T&"); supports_flags::set_flags(bits); } basic_flags_t(const U& bits) { TRACE_CTOR(basic_flags_t, "const U&"); supports_flags::set_flags(static_cast(bits)); } ~basic_flags_t() throw() { TRACE_DTOR(basic_flags_t); } basic_flags_t(const basic_flags_t& other) : supports_flags(other) { TRACE_CTOR(basic_flags_t, "copy"); } basic_flags_t& operator=(const basic_flags_t& other) { set_flags(other.flags()); return *this; } basic_flags_t& operator=(const T& bits) { set_flags(bits); return *this; } operator T() const { return supports_flags::flags(); } operator U() const { return supports_flags::flags(); } basic_flags_t plus_flags(const T& arg) const { basic_flags_t temp(*this); temp.add_flags(arg); return temp; } basic_flags_t minus_flags(const T& arg) const { basic_flags_t temp(*this); temp.drop_flags(arg); return temp; } }; template class delegates_flags : public boost::noncopyable { public: typedef T flags_t; protected: supports_flags& _flags; public: delegates_flags() : _flags() { TRACE_CTOR(delegates_flags, ""); } delegates_flags(supports_flags& arg) : _flags(arg) { TRACE_CTOR(delegates_flags, "const supports_flags&"); } ~delegates_flags() throw() { TRACE_DTOR(delegates_flags); } flags_t flags() const { return _flags.flags(); } bool has_flags(const flags_t arg) const { return _flags.has_flags(arg); } void set_flags(const flags_t arg) { _flags.set_flags(arg); } void clear_flags() { _flags.clear_flags(); } void add_flags(const flags_t arg) { _flags.add_flags(arg); } void drop_flags(const flags_t arg) { _flags.drop_flags(arg); } }; #endif // _FLAGS_H ledger-3.1.1+dfsg1/src/format.cc000066400000000000000000000540511266023441000163470ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "format.h" #include "scope.h" #include "pstream.h" namespace ledger { format_t::elision_style_t format_t::default_style = TRUNCATE_TRAILING; bool format_t::default_style_changed = false; void format_t::element_t::dump(std::ostream& out) const { out << "Element: "; switch (type) { case STRING: out << " STRING"; break; case EXPR: out << " EXPR"; break; } out << " flags: 0x" << std::hex << int(flags()); out << " min: "; out << std::right; out.width(2); out << std::dec << int(min_width); out << " max: "; out << std::right; out.width(2); out << std::dec << int(max_width); switch (type) { case STRING: out << " str: '" << boost::get(data) << "'" << std::endl; break; case EXPR: out << " expr: " << boost::get(data) << std::endl; break; } } namespace { struct format_mapping_t { char letter; const char * expr; } single_letter_mappings[] = { { 'd', "aux_date ? format_date(date) + \"=\" + format_date(aux_date) : format_date(date)" }, { 'D', "date" }, { 'S', "filename" }, { 'B', "beg_pos" }, { 'b', "beg_line" }, { 'E', "end_pos" }, { 'e', "end_line" }, { 'X', "\"* \" if cleared" }, { 'Y', "\"* \" if xact.cleared" }, { 'C', "\"(\" + code + \") \" if code" }, { 'P', "payee" }, { 'a', "account" }, { 'A', "account" }, { 't', "justify(scrub(display_amount), $min, $max, $left, color)" }, { 'T', "justify(scrub(display_total), $min, $max, $left, color)" }, { 'N', "note" }, }; expr_t parse_single_expression(const char *& p, bool single_expr = true) { string temp(p); ptristream str(const_cast(p)); expr_t expr; expr.parse(str, single_expr ? PARSE_SINGLE : PARSE_PARTIAL, temp); if (str.eof()) { expr.set_text(p); p += std::strlen(p); } else { assert(str.good()); istream_pos_type pos = str.tellg(); expr.set_text(string(p, p + long(pos))); p += long(pos) - 1; // Don't gobble up any whitespace const char * base = p; while (p >= base && std::isspace(*p)) p--; } return expr; } inline expr_t::ptr_op_t ident_node(const string& ident) { expr_t::ptr_op_t node(new expr_t::op_t(expr_t::op_t::IDENT)); node->set_ident(ident); return node; } } format_t::element_t * format_t::parse_elements(const string& fmt, const optional& tmpl) { unique_ptr result; element_t * current = NULL; static char buf[65535]; char * q = buf; for (const char * p = fmt.c_str(); *p; p++) { if (*p != '%' && *p != '\\') { *q++ = *p; continue; } if (! result.get()) { result.reset(new element_t); current = result.get(); } else { current->next.reset(new element_t); current = current->next.get(); } if (q != buf) { current->type = element_t::STRING; current->data = string(buf, q); q = buf; current->next.reset(new element_t); current = current->next.get(); } if (*p == '\\') { p++; current->type = element_t::STRING; switch (*p) { case 'b': current->data = string("\b"); break; case 'f': current->data = string("\f"); break; case 'n': current->data = string("\n"); break; case 'r': current->data = string("\r"); break; case 't': current->data = string("\t"); break; case 'v': current->data = string("\v"); break; case '\\': current->data = string("\\"); break; default: current->data = string(1, *p); break; } continue; } ++p; while (*p == '-') { switch (*p) { case '-': current->add_flags(ELEMENT_ALIGN_LEFT); break; } ++p; } std::size_t num = 0; while (*p && std::isdigit(*p)) { num *= 10; num += static_cast(*p++ - '0'); } current->min_width = num; if (*p == '.') { ++p; num = 0; while (*p && std::isdigit(*p)) { num *= 10; num += static_cast(*p++ - '0'); } current->max_width = num; if (current->min_width == 0) current->min_width = current->max_width; } if (std::isalpha(*p)) { bool found = false; for (std::size_t i = 0; i < (sizeof(single_letter_mappings) / sizeof(format_mapping_t)); i++) { if (*p == single_letter_mappings[i].letter) { std::ostringstream expr; for (const char * ptr = single_letter_mappings[i].expr; *ptr;) { if (*ptr == '$') { const char * beg = ++ptr; while (*ptr && std::isalpha(*ptr)) ++ptr; string::size_type klen = static_cast(ptr - beg); string keyword(beg, 0, klen); if (keyword == "min") expr << (current->min_width > 0 ? static_cast(current->min_width) : -1); else if (keyword == "max") expr << (current->max_width > 0 ? static_cast(current->max_width) : -1); else if (keyword == "left") expr << (current->has_flags(ELEMENT_ALIGN_LEFT) ? "false" : "true"); #if DEBUG_ON else assert("Unrecognized format substitution keyword" == NULL); #endif } else { expr << *ptr++; } } current->type = element_t::EXPR; current->data = expr_t(expr.str()); found = true; break; } } if (! found) throw_(format_error, _f("Unrecognized formatting character: %1%") % *p); } else { switch (*p) { case '%': current->type = element_t::STRING; current->data = string("%"); break; case '$': { if (! tmpl) throw_(format_error, _("Prior field reference, but no template")); p++; if (*p == '0' || (! std::isdigit(*p) && *p != 'A' && *p != 'B' && *p != 'C' && *p != 'D' && *p != 'E' && *p != 'F')) throw_(format_error, _("%$ field reference must be a digit from 1-9")); int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10); element_t * tmpl_elem = tmpl->elements.get(); for (int i = 1; i < index && tmpl_elem; i++) { tmpl_elem = tmpl_elem->next.get(); while (tmpl_elem && tmpl_elem->type != element_t::EXPR) tmpl_elem = tmpl_elem->next.get(); } if (! tmpl_elem) throw_(format_error, _("%$ reference to a non-existent prior field")); *current = *tmpl_elem; break; } case '(': case '{': { bool format_amount = *p == '{'; current->type = element_t::EXPR; current->data = parse_single_expression(p); // Wrap the subexpression in calls to justify and scrub if (! format_amount) break; expr_t::ptr_op_t op = boost::get(current->data).get_op(); expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL)); { call2_node->set_left(ident_node("justify")); { expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS)); { { expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL)); { call1_node->set_left(ident_node("scrub")); call1_node->set_right(op->kind == expr_t::op_t::O_CONS ? op->left() : op); } args3_node->set_left(call1_node); } expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS)); { { expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE)); arg1_node->set_value(current->min_width > 0 ? long(current->min_width) : -1); args2_node->set_left(arg1_node); } { expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS)); { { expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE)); arg2_node->set_value(current->max_width > 0 ? long(current->max_width) : -1); args1_node->set_left(arg2_node); } { expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE)); arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT)); args1_node->set_right(arg3_node); } } args2_node->set_right(args1_node); } args3_node->set_right(args2_node); } } call2_node->set_right(args3_node); } } current->min_width = 0; current->max_width = 0; string prev_expr = boost::get(current->data).text(); expr_t::ptr_op_t colorize_op; if (op->kind == expr_t::op_t::O_CONS) colorize_op = op->has_right() ? op->right() : NULL; if (colorize_op) { expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL)); { call3_node->set_left(ident_node("ansify_if")); { expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS)); { args4_node->set_left(call2_node); // from above args4_node->set_right(colorize_op); } call3_node->set_right(args4_node); } } current->data = expr_t(call3_node); } else { current->data = expr_t(call2_node); } boost::get(current->data).set_text(prev_expr); break; } default: throw_(format_error, _f("Unrecognized formatting character: %1%") % *p); } } } if (q != buf) { if (! result.get()) { result.reset(new element_t); current = result.get(); } else { current->next.reset(new element_t); current = current->next.get(); } current->type = element_t::STRING; current->data = string(buf, q); } return result.release(); } string format_t::real_calc(scope_t& scope) { std::ostringstream out_str; for (element_t * elem = elements.get(); elem; elem = elem->next.get()) { std::ostringstream out; string name; if (elem->has_flags(ELEMENT_ALIGN_LEFT)) out << std::left; else out << std::right; switch (elem->type) { case element_t::STRING: if (elem->min_width > 0) out.width(static_cast(elem->min_width)); out << boost::get(elem->data); break; case element_t::EXPR: { expr_t& expr(boost::get(elem->data)); try { expr.compile(scope); value_t value; if (expr.is_function()) { call_scope_t args(scope); args.push_back(long(elem->max_width)); value = expr.get_function()(args); } else { value = expr.calc(scope); } DEBUG("format.expr", "value = (" << value << ")"); if (elem->min_width > 0) value.print(out, static_cast(elem->min_width), -1, ! elem->has_flags(ELEMENT_ALIGN_LEFT)); else out << value.to_string(); } catch (const calc_error&) { string current_context = error_context(); add_error_context(_("While calculating format expression:")); add_error_context(expr.context_to_str()); if (! current_context.empty()) add_error_context(current_context); throw; } break; } } if (elem->max_width > 0 || elem->min_width > 0) { unistring temp(out.str()); string result; if (elem->max_width > 0 && elem->max_width < temp.length()) { result = truncate(temp, elem->max_width); } else { result = temp.extract(); if (elem->min_width > temp.length()) for (std::size_t i = 0; i < elem->min_width - temp.length(); i++) result += " "; } out_str << result; } else { out_str << out.str(); } } return out_str.str(); } string format_t::truncate(const unistring& ustr, const std::size_t width, const std::size_t account_abbrev_length) { assert(width < 4095); const std::size_t len = ustr.length(); if (width == 0 || len <= width) return ustr.extract(); std::ostringstream buf; elision_style_t style = default_style; if (account_abbrev_length > 0 && ! default_style_changed) style = ABBREVIATE; switch (style) { case TRUNCATE_LEADING: // This method truncates at the beginning. buf << ".." << ustr.extract(len - (width - 2), width - 2); break; case TRUNCATE_MIDDLE: // This method truncates in the middle. buf << ustr.extract(0, (width - 2) / 2) << ".." << ustr.extract(len - ((width - 2) / 2 + (width - 2) % 2), (width - 2) / 2 + (width - 2) % 2); break; case ABBREVIATE: if (account_abbrev_length > 0) { // The algorithm here is complex, but aims to preserve the most // information in the most useful places. // // Consider: You have an account name like // 'Assets:Banking:Check:Register'. This account name, which is // 29 characters long, must be shortened to fit in 20. How would // you shorten it? // // The approach taken below is to compute the difference, or 9 // characters, and then distribute this difference semi-evenly // among first three segments of the account name, by taking // characters until the difference is gone. Further, earlier // segments will give up more of their share of letters than later // segments, since the later segments usually contain more useful // information. // First, chop up the Unicode string into individual segments. std::list parts; string::size_type beg = 0; string strcopy(ustr.extract()); for (string::size_type pos = strcopy.find(':'); pos != string::npos; beg = pos + 1, pos = strcopy.find(':', beg)) parts.push_back(string(strcopy, beg, pos - beg)); parts.push_back(string(strcopy, beg)); DEBUG("format.abbrev", "Account name: " << strcopy); DEBUG("format.abbrev", "Must fit a " << len << " char string in " << width << " chars"); // Figure out the lengths of all the parts. The last part is // always displayed in full, while the former parts are // distributed, with the latter parts being longer than the // former, but with none shorter than account_abbrev_length. std::list lens; #if DEBUG_ON int index = 0; #endif for (std::list::iterator i = parts.begin(); i != parts.end(); i++) { std::size_t l = unistring(*i).length(); DEBUG("format.abbrev", "Segment " << ++index << " is " << l << " chars wide"); lens.push_back(l); } // Determine the "overflow", or how many chars in excess we are. std::size_t overflow = len - width; DEBUG("format.abbrev", "There are " << overflow << " chars of overflow"); // Walk through the first n-1 segments, and start subtracting // letters to decrease the overflow. This is done in multiple // passes until the overflow is gone, or we cannot reduce any // further. The calculation to find the amount to remove is: // // overflow * (((len(segment) + counter) * iteration) / // (len(string) - len(last_segment) - counter)) // // Where: // overflow - the amount that needs to be removed // counter - starts at n-1 for the first segment, then // decreases by one until it reaches 0 for the // last segment (which is never shortened). // This value is used to weight the shrinkage // so that earlier segments shrink faster. // iteration - starts at 1, increase by 1 for every // iteration of the loop // // In the example above, we have this account name: // // Assets:Banking:Check:Register // // Therefore, the amount to be removed from Assets is calculated as: // // 9 * (((6 + 3) * 1) / (29 - 8 - 3)) = ceil(4.5) = 5 // // However, since removing 5 chars would make the length of the // segment shorter than the default minimum of 2, we can only // remove 4 chars from Assets to reduce the overflow. And on it // goes. // // The final result will be: As:Ban:Chec:Register std::size_t iteration = 1; std::size_t len_minus_last = len - lens.back(); while (overflow > 0) { std::size_t overflow_at_start = overflow; DEBUG("format.abbrev", "Overflow starting at " << overflow << " chars"); #if DEBUG_ON index = 0; #endif std::size_t counter = lens.size(); std::list::iterator x = parts.begin(); for (std::list::iterator i = lens.begin(); i != lens.end(); i++) { if (--counter == 0 || overflow == 0) break; DEBUG("format.abbrev", "Overflow is " << overflow << " chars"); std::size_t adjust; if (overflow == 1) adjust = 1; else adjust = std::size_t (std::ceil(double(overflow) * ((double(*i + counter*3) * double(iteration)) / (double(len_minus_last) - double(counter))))); DEBUG("format.abbrev", "Weight calc: (" << overflow << " * (((" << *i << " + " << counter << ") * " << iteration << ") / (" << len_minus_last << " - " << counter << ")))"); if (adjust == 0) adjust = 1; else if (adjust > overflow) adjust = overflow; DEBUG("format.abbrev", "The weighted part is " << adjust << " chars"); std::size_t slack = *i - std::min(*i, account_abbrev_length); if (adjust > slack) adjust = slack; if (adjust > 0) { DEBUG("format.abbrev", "Reducing segment " << ++index << " by " << adjust << " chars"); while (std::isspace((*x)[*i - adjust - 1]) && adjust < *i) { DEBUG("format.abbrev", "Segment ends in whitespace, adjusting down"); ++adjust; } (*i) -= adjust; DEBUG("format.abbrev", "Segment " << index << " is now " << *i << " chars wide"); if (adjust > overflow) overflow = 0; else overflow -= adjust; DEBUG("format.abbrev", "Overflow is now " << overflow << " chars"); } ++x; } DEBUG("format.abbrev", "Overflow ending this time at " << overflow << " chars"); if (overflow == overflow_at_start) break; iteration++; } assert(parts.size() == lens.size()); std::list::iterator i = parts.begin(); std::list::iterator l = lens.begin(); std::ostringstream result; for (; i != parts.end() && l != lens.end(); i++, l++) { std::list::iterator x = i; if (++x == parts.end()) { result << *i; break; } unistring temp(*i); if (temp.length() > *l) result << temp.extract(0, *l) << ":"; else result << *i << ":"; } if (overflow > 0) { // Even abbreviated its too big to show the last account, so // abbreviate all but the last and truncate at the beginning. unistring temp(result.str()); assert(temp.length() > width - 2); buf << ".." << temp.extract(temp.length() - (width - 2), width - 2); } else { buf << result.str(); } break; } // fall through... case TRUNCATE_TRAILING: // This method truncates at the end (the default). buf << ustr.extract(0, width - 2) << ".."; break; } return buf.str(); } } // namespace ledger ledger-3.1.1+dfsg1/src/format.h000066400000000000000000000112111266023441000162000ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup expr */ /** * @file format.h * @author John Wiegley * * @ingroup expr */ #ifndef _FORMAT_H #define _FORMAT_H #include "expr.h" #include "unistring.h" namespace ledger { class unistring; DECLARE_EXCEPTION(format_error, std::runtime_error); class format_t : public expr_base_t, public noncopyable { typedef expr_base_t base_type; struct element_t : public supports_flags<>, public noncopyable { #define ELEMENT_ALIGN_LEFT 0x01 enum kind_t { STRING, EXPR }; kind_t type; std::size_t min_width; std::size_t max_width; variant data; scoped_ptr next; element_t() throw() : supports_flags<>(), type(STRING), min_width(0), max_width(0) { TRACE_CTOR(element_t, ""); } ~element_t() throw() { TRACE_DTOR(element_t); } element_t& operator=(const element_t& elem) { if (this != &elem) { supports_flags<>::operator=(elem); type = elem.type; min_width = elem.min_width; max_width = elem.max_width; data = elem.data; } return *this; } friend inline void mark_red(std::ostream& out, const element_t * elem) { out.setf(std::ios::left); out.width(0); out << "\033[31m"; if (elem->has_flags(ELEMENT_ALIGN_LEFT)) out << std::left; else out << std::right; if (elem->min_width > 0) out.width(static_cast(elem->min_width)); } void dump(std::ostream& out) const; }; scoped_ptr elements; public: static enum elision_style_t { TRUNCATE_TRAILING, TRUNCATE_MIDDLE, TRUNCATE_LEADING, ABBREVIATE } default_style; static bool default_style_changed; private: static element_t * parse_elements(const string& fmt, const optional& tmpl); public: format_t() : base_type() { TRACE_CTOR(format_t, ""); } format_t(const string& _str, scope_t * context = NULL) : base_type(context) { if (! _str.empty()) parse_format(_str); TRACE_CTOR(format_t, "const string&"); } virtual ~format_t() { TRACE_DTOR(format_t); } void parse_format(const string& _format, const optional& tmpl = none) { elements.reset(parse_elements(_format, tmpl)); set_text(_format); } virtual void mark_uncompiled() { for (element_t * elem = elements.get(); elem; elem = elem->next.get()) { if (elem->type == element_t::EXPR) { expr_t& expr(boost::get(elem->data)); expr.mark_uncompiled(); } } } virtual result_type real_calc(scope_t& scope); virtual void dump(std::ostream& out) const { for (const element_t * elem = elements.get(); elem; elem = elem->next.get()) elem->dump(out); } static string truncate(const unistring& str, const std::size_t width, const std::size_t account_abbrev_length = 0); }; } // namespace ledger #endif // _FORMAT_H ledger-3.1.1+dfsg1/src/generate.cc000066400000000000000000000241671266023441000166560ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "generate.h" #include "session.h" namespace ledger { generate_posts_iterator::generate_posts_iterator (session_t& _session, unsigned int _seed, std::size_t _quantity) : session(_session), seed(_seed), quantity(_quantity), rnd_gen(seed == 0 ? static_cast(std::time(0)) : seed), year_range(1900, 2300), year_gen(rnd_gen, year_range), mon_range(1, 12), mon_gen(rnd_gen, mon_range), day_range(1, 28), day_gen(rnd_gen, day_range), upchar_range('A', 'Z'), upchar_gen(rnd_gen, upchar_range), downchar_range('a', 'z'), downchar_gen(rnd_gen, downchar_range), numchar_range('0', '9'), numchar_gen(rnd_gen, numchar_range), truth_range(0, 1), truth_gen(rnd_gen, truth_range), three_range(1, 3), three_gen(rnd_gen, three_range), six_range(1, 6), six_gen(rnd_gen, six_range), two_six_range(2, 6), two_six_gen(rnd_gen, two_six_range), strlen_range(1, 40), strlen_gen(rnd_gen, strlen_range), neg_number_range(-10000, -1), neg_number_gen(rnd_gen, neg_number_range), pos_number_range(1, 10000), pos_number_gen(rnd_gen, pos_number_range) { std::ostringstream next_date_buf; generate_date(next_date_buf); next_date = parse_date(next_date_buf.str()); std::ostringstream next_aux_date_buf; generate_date(next_aux_date_buf); next_aux_date = parse_date(next_aux_date_buf.str()); TRACE_CTOR(generate_posts_iterator, "bool"); } void generate_posts_iterator::generate_string(std::ostream& out, int len, bool only_alpha) { DEBUG("generate.post.string", "Generating string of length " << len << ", only alpha " << only_alpha); int last = -1; bool first = true; for (int i = 0; i < len; i++) { int next = only_alpha ? 3 : three_gen(); bool output = true; switch (next) { case 1: // colon if (! first && last == 3 && strlen_gen() % 10 == 0 && i + 1 != len) out << ':'; else { i--; output = false; } break; case 2: // space if (! first && last == 3 && strlen_gen() % 20 == 0 && i + 1 != len) out << ' '; else { i--; output = false; } break; case 3: // character switch (three_gen()) { case 1: // uppercase out << char(upchar_gen()); break; case 2: // lowercase out << char(downchar_gen()); break; case 3: // number if (! only_alpha && ! first) out << char(numchar_gen()); else { i--; output = false; } break; } break; } if (output) { last = next; first = false; } } } bool generate_posts_iterator::generate_account(std::ostream& out, bool no_virtual) { bool must_balance = true; bool is_virtual = false; if (! no_virtual) { switch (three_gen()) { case 1: out << '['; is_virtual = true; break; case 2: out << '('; must_balance = false; is_virtual = true; break; case 3: break; } } generate_string(out, strlen_gen()); if (is_virtual) { if (must_balance) out << ']'; else out << ')'; } return must_balance; } void generate_posts_iterator::generate_commodity(std::ostream& out, const string& exclude) { string comm; do { std::ostringstream buf; generate_string(buf, six_gen(), true); comm = buf.str(); } while (comm == exclude || comm == "h" || comm == "m" || comm == "s" || comm == "and" || comm == "any" || comm == "all" || comm == "div" || comm == "false" || comm == "or" || comm == "not" || comm == "true" || comm == "if" || comm == "else"); out << comm; } string generate_posts_iterator::generate_amount(std::ostream& out, value_t not_this_amount, bool no_negative, const string& exclude) { std::ostringstream buf; if (truth_gen()) { // commodity goes in front generate_commodity(buf, exclude); if (truth_gen()) buf << ' '; if (no_negative || truth_gen()) buf << pos_number_gen(); else buf << neg_number_gen(); } else { if (no_negative || truth_gen()) buf << pos_number_gen(); else buf << neg_number_gen(); if (truth_gen()) buf << ' '; generate_commodity(buf, exclude); } // Possibly generate an annotized commodity, but make it rarer if (! no_negative && three_gen() == 1) { if (three_gen() == 1) { buf << " {"; generate_amount(buf, value_t(), true); buf << '}'; } if (six_gen() == 1) { buf << " ["; generate_date(buf); buf << ']'; } if (six_gen() == 1) { buf << " ("; generate_string(buf, six_gen()); buf << ')'; } } if (! not_this_amount.is_null() && value_t(buf.str()).as_amount().commodity() == not_this_amount.as_amount().commodity()) return ""; out << buf.str(); return buf.str(); } bool generate_posts_iterator::generate_post(std::ostream& out, bool no_amount) { out << " "; bool must_balance = generate_account(out, no_amount); out << " "; if (! no_amount) { value_t amount(generate_amount(out)); if (truth_gen()) generate_cost(out, amount); } if (truth_gen()) generate_note(out); out << '\n'; return must_balance; } void generate_posts_iterator::generate_cost(std::ostream& out, value_t amount) { std::ostringstream buf; if (truth_gen()) buf << " @ "; else buf << " @@ "; if (! generate_amount(buf, amount, true, amount.as_amount().commodity().symbol()).empty()) out << buf.str(); } void generate_posts_iterator::generate_date(std::ostream& out) { out.width(4); out.fill('0'); out << year_gen(); out.width(1); out << '/'; out.width(2); out.fill('0'); out << mon_gen(); out.width(1); out << '/'; out.width(2); out.fill('0'); out << day_gen(); } void generate_posts_iterator::generate_state(std::ostream& out) { switch (three_gen()) { case 1: out << "* "; break; case 2: out << "! "; break; case 3: out << ""; break; } } void generate_posts_iterator::generate_code(std::ostream& out) { out << '('; generate_string(out, six_gen()); out << ") "; } void generate_posts_iterator::generate_payee(std::ostream& out) { generate_string(out, strlen_gen()); } void generate_posts_iterator::generate_note(std::ostream& out) { out << "\n ; "; generate_string(out, strlen_gen()); } void generate_posts_iterator::generate_xact(std::ostream& out) { out << format_date(next_date, FMT_WRITTEN); next_date += gregorian::days(six_gen()); if (truth_gen()) { out << '='; out << format_date(next_aux_date, FMT_WRITTEN); next_aux_date += gregorian::days(six_gen()); } out << ' '; generate_state(out); generate_code(out); generate_payee(out); if (truth_gen()) generate_note(out); out << '\n'; int count = three_gen() * 2; bool has_must_balance = false; for (int i = 0; i < count; i++) { if (generate_post(out)) has_must_balance = true; } if (has_must_balance) generate_post(out, true); out << '\n'; } void generate_posts_iterator::increment() { post_t * post = *posts++; if (post == NULL && quantity > 0) { std::ostringstream buf; generate_xact(buf); DEBUG("generate.post", "The post we intend to parse:\n" << buf.str()); try { shared_ptr in(new std::istringstream(buf.str())); parse_context_stack_t parsing_context; parsing_context.push(in); parsing_context.get_current().journal = session.journal.get(); parsing_context.get_current().scope = &session; if (session.journal->read(parsing_context) != 0) { VERIFY(session.journal->xacts.back()->valid()); posts.reset(*session.journal->xacts.back()); post = *posts++; } } catch (std::exception&) { add_error_context(_f("While parsing generated transaction (seed %1%):") % seed); add_error_context(buf.str()); throw; } catch (int) { add_error_context(_f("While parsing generated transaction (seed %1%):") % seed); add_error_context(buf.str()); throw; } quantity--; } m_node = post; } } // namespace ledger ledger-3.1.1+dfsg1/src/generate.h000066400000000000000000000103121266023441000165030ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, 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. */ /** * @addtogroup generate */ /** * @file generate.h * @author John Wiegley * * @ingroup report */ #ifndef _GENERATE_H #define _GENERATE_H #include "iterators.h" namespace ledger { class session_t; class generate_posts_iterator : public iterator_facade_base { session_t& session; unsigned int seed; std::size_t quantity; date_t next_date; date_t next_aux_date; mt19937 rnd_gen; typedef variate_generator > int_generator_t; typedef variate_generator > real_generator_t; uniform_int<> year_range; int_generator_t year_gen; uniform_int<> mon_range; int_generator_t mon_gen; uniform_int<> day_range; int_generator_t day_gen; uniform_int<> upchar_range; int_generator_t upchar_gen; uniform_int<> downchar_range; int_generator_t downchar_gen; uniform_int<> numchar_range; int_generator_t numchar_gen; uniform_int<> truth_range; int_generator_t truth_gen; uniform_int<> three_range; int_generator_t three_gen; uniform_int<> six_range; int_generator_t six_gen; uniform_int<> two_six_range; int_generator_t two_six_gen; uniform_int<> strlen_range; int_generator_t strlen_gen; uniform_real<> neg_number_range; real_generator_t neg_number_gen; uniform_real<> pos_number_range; real_generator_t pos_number_gen; xact_posts_iterator posts; public: generate_posts_iterator(session_t& _session, unsigned int _seed = 0, std::size_t _quantity = 100); virtual ~generate_posts_iterator() throw() { TRACE_DTOR(generate_posts_iterator); } virtual void increment(); protected: void generate_string(std::ostream& out, int len, bool only_alpha = false); bool generate_account(std::ostream& out, bool no_virtual = false); void generate_commodity(std::ostream& out, const string& exclude = ""); string generate_amount(std::ostream& out, value_t not_this_amount = NULL_VALUE, bool no_negative = false, const string& exclude = ""); bool generate_post(std::ostream& out, bool no_amount = false); void generate_cost(std::ostream& out, value_t amount); void generate_date(std::ostream& out); void generate_state(std::ostream& out); void generate_code(std::ostream& out); void generate_payee(std::ostream& out); void generate_note(std::ostream& out); void generate_xact(std::ostream& out); }; } // namespace ledger #endif // _GENERATE_H ledger-3.1.1+dfsg1/src/global.cc000066400000000000000000000364521266023441000163240ustar00rootroot00000000000000/* * Copyright (c) 2003-2016, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of New Artisans LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "global.h" #if HAVE_BOOST_PYTHON #include "pyinterp.h" #else #include "session.h" #endif #include "item.h" #include "journal.h" #include "pool.h" namespace ledger { static bool args_only = false; std::string _init_file; global_scope_t::global_scope_t(char ** envp) { epoch = CURRENT_TIME(); #if HAVE_BOOST_PYTHON if (! python_session.get()) { python_session.reset(new ledger::python_interpreter_t); session_ptr = python_session; } #else session_ptr.reset(new session_t); #endif set_session_context(session_ptr.get()); // Create the report object, which maintains state relating to each // command invocation. Because we're running from main(), the // distinction between session and report doesn't really matter, but if // a GUI were calling into Ledger it would have one session object per // open document, with a separate report_t object for each report it // generated. report_stack.push_front(new report_t(*session_ptr)); scope_t::default_scope = &report(); scope_t::empty_scope = &empty_scope; // Read the user's options, in the following order: // // 1. environment variables (LEDGER_