diff options
59 files changed, 535 insertions, 586 deletions
diff --git a/.travis.yml b/.travis.yml index 94489bdc..ae2ff727 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,3 @@ -# 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 @@ -8,51 +5,22 @@ 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.61.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<char const*, std::allocator<boost::sub_match<char const*> >, boost::regex_traits<char, boost::cpp_regex_traits<char> > >::construct_init(boost::basic_regex<char, boost::regex_traits<char, boost::cpp_regex_traits<char> > > const&, boost::regex_constants::_match_flags)", referenced from: - # boost::re_detail::perl_matcher<char const*, std::allocator<boost::sub_match<char const*> >, boost::regex_traits<char, boost::cpp_regex_traits<char> > >::perl_matcher(char const*, char const*, boost::match_results<char const*, std::allocator<boost::sub_match<char const*> > >&, boost::basic_regex<char, boost::regex_traits<char, boost::cpp_regex_traits<char> > > const&, boost::regex_constants::_match_flags, char const*) in main.cc.o - # boost::re_detail::perl_matcher<char const*, std::allocator<boost::sub_match<char const*> >, boost::regex_traits<char, boost::cpp_regex_traits<char> > >::perl_matcher(char const*, char const*, boost::match_results<char const*, std::allocator<boost::sub_match<char const*> > >&, boost::basic_regex<char, boost::regex_traits<char, boost::cpp_regex_traits<char> > > const&, boost::regex_constants::_match_flags, char const*) in global.cc.o - # "boost::re_detail::perl_matcher<char const*, std::allocator<boost::sub_match<char const*> >, boost::regex_traits<char, boost::cpp_regex_traits<char> > >::find()", referenced from: - # bool boost::regex_search<char const*, char, boost::regex_traits<char, boost::cpp_regex_traits<char> > >(char const*, char const*, boost::basic_regex<char, boost::regex_traits<char, boost::cpp_regex_traits<char> > > const&, boost::regex_constants::_match_flags) in main.cc.o - # bool boost::regex_search<char const*, char, boost::regex_traits<char, boost::cpp_regex_traits<char> > >(char const*, char const*, boost::basic_regex<char, boost::regex_traits<char, boost::cpp_regex_traits<char> > > const&, boost::regex_constants::_match_flags) in global.cc.o - - os: osx - compiler: clang - # On Mac OS X building ledger with clang fails due to - # dyld: Library not loaded: libboost_python.dylib - # Referenced from: /Users/travis/build/ledger/ledger/ledger - # Reason: image not found + matrix: + # Boost version to build against; an empty string means the + # distribution's default. + - BOOST_VERSION="1.61.0" addons: coverity_scan: @@ -63,36 +31,39 @@ addons: 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 + - libboost-dev + - libboost-test-dev + - libboost-regex-dev + - libboost-python-dev + - libboost-system-dev + - libboost-date-time-dev + - libboost-iostreams-dev + - libboost-filesystem-dev + - libboost-serialization-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 + - | + if [ -n "${BOOST_VERSION}" ]; then + BOOST_SOURCE="$(mktemp -d)" + BOOST_SOURCE_URL="https://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION//./_}.tar.bz2/download" + curl -Ls "$BOOST_SOURCE_URL" | + tar jx -C "${BOOST_SOURCE}" --strip-components 1 + fi install: - - tools/travis-install.sh + - | + if [ -n "${BOOST_VERSION}" ]; then + export BOOST_ROOT="${HOME}/boost" + pushd "${BOOST_SOURCE}" + ./bootstrap.sh --with-libraries="${BOOST_LIBS}" + ./b2 threading=multi -d0 --prefix="${BOOST_ROOT}" install + popd + rm -Rf "${BOOST_SOURCE}" + fi before_script: - cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON @@ -102,12 +73,6 @@ 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index f8dbd892..c85a5157 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,8 +96,6 @@ include(CheckCXXSourceCompiles) 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) @@ -260,7 +258,7 @@ macro(add_ledger_library_dependencies _target) 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 + # Don't link directly to a Python framework on macOS, 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") @@ -57,7 +57,7 @@ A: You're probably missing some dependency libraries. If you tried 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 + doxygen libedit-dev libmpc-dev tzdata ---------------------------------------------------------------------- @@ -115,7 +115,7 @@ 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 + bug on macOS systems where boost_python is linked against the default Python, while Ledger is linked against the version provided by MacPorts. Or vice versa. @@ -84,19 +84,9 @@ Dependency | Version (or greater) [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: +### macOS -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. +You can use [Homebrew] or [MacPorts] to install Ledger easily on macOS. #### 1. Homebrew @@ -111,7 +101,7 @@ If you to want to startup python, use the following command: #### 2. MacPorts -If you build stuff using MacPorts on OS X, as I do, here is what you would +If you build stuff using MacPorts on macOS, as I do, here is what you would run: $ sudo port install -f cmake python26 \ @@ -124,30 +114,19 @@ run: ### Ubuntu If you're going to build on Ubuntu, `sudo apt-get install ...` the -following packages (current as of Ubuntu 14.04): +following packages (current as of Ubuntu 18.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 + libboost-test-dev libedit-dev libgmp3-dev libmpfr-dev texinfo tzdata ### 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 9 (stretch), Debian testing -and Debian unstable (sid) contain all components needed to build ledger. -You can install all required build dependencies using the following command: +Debian 9 (stretch), Debian 10 (buster), Debian testing 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 \ @@ -166,7 +145,7 @@ footwork for you: # $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 +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 @@ -53,16 +53,17 @@ def which(program): class BoostInfo(object): def dependencies(self, system): - if system == 'darwin-homebrew': + if system in ['darwin-homebrew']: return [ 'boost' ] - if system == 'darwin-macports': + if system in ['darwin-macports']: return [ 'boost-jam', 'boost', '+python27+universal' ] - if system == 'centos': + if system in ['centos']: return [ 'boost-devel' ] - elif system == 'ubuntu-xenial': + elif system in ['ubuntu-bionic', 'ubuntu-xenial', + 'ubuntu-trusty', 'ubuntu-cosmic']: return [ 'libboost-dev', 'libboost-date-time-dev', 'libboost-filesystem-dev', @@ -70,19 +71,10 @@ class BoostInfo(object): 'libboost-python-dev', 'libboost-regex-dev', 'libboost-system-dev', - 'libboost-test-dev' ] - - 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' ] + 'libboost-test-dev', + 'tzdata' ] - elif system == 'ubuntu-saucy' or system == 'ubuntu-precise': + elif system in [ 'ubuntu-saucy', 'ubuntu-precise']: return [ 'autopoint', 'libboost-dev', 'libboost-test-dev', @@ -92,7 +84,7 @@ class BoostInfo(object): 'libboost-iostreams-dev', 'libboost-python-dev' ] - elif system == 'ubuntu-lucid': + elif system in ['ubuntu-lucid']: return [ 'bjam', 'autopoint', 'libboost-dev', 'libboost-regex-dev', @@ -510,11 +502,11 @@ class PrepareBuild(CommandLineApp): self.log.info("Installing Ledger's build dependencies ...") - system = self.get_stdout('uname', '-s') + system = self.get_stdout('uname', '-s').decode('utf8') if system == 'Darwin': if exists('/opt/local/bin/port'): - self.log.info('Looks like you are using MacPorts on OS X') + self.log.info('Looks like you are using MacPorts on macOS') packages = [ 'sudo', 'port', 'install', '-f', 'automake', 'autoconf', 'libtool', @@ -531,7 +523,7 @@ class PrepareBuild(CommandLineApp): 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') + self.log.info('Looks like you are using Homebrew on macOS') packages = [ 'brew', 'install', 'cmake', 'ninja', @@ -540,7 +532,7 @@ class PrepareBuild(CommandLineApp): 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.info('Looks like you are using Fink on macOS') self.log.error("I don't know the package names for Fink yet!") sys.exit(1) @@ -548,14 +540,17 @@ class PrepareBuild(CommandLineApp): 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', + info = dict([line.strip().split('=', 1) + for line in open('/etc/lsb-release')]) + release = info['DISTRIB_CODENAME'] + self.log.info('Looks like you are using APT on Ubuntu ' + release) + packages = [ + 'sudo', 'apt-get', 'install', + 'build-essential', + ] + + if release == 'bionic': + packages.extend([ 'doxygen', 'cmake', 'ninja-build', @@ -570,12 +565,9 @@ class PrepareBuild(CommandLineApp): 'lcov', 'libutfcpp-dev', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-trusty') - elif re.search('xenial', info): - self.log.info('Looks like you are using APT on Ubuntu Xenial') - packages = [ - 'sudo', 'apt-get', 'install', - 'build-essential', + ]) + elif release == 'trusty': + packages.extend([ 'doxygen', 'cmake', 'ninja-build', @@ -590,13 +582,27 @@ class PrepareBuild(CommandLineApp): 'lcov', 'libutfcpp-dev', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-xenial') - 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', + ]) + elif release == 'xenial': + packages.extend([ + 'doxygen', + 'cmake', + 'ninja-build', + 'zlib1g-dev', + 'libbz2-dev', + 'python-dev', + 'libgmp3-dev', + 'libmpfr-dev', + 'gettext', + 'libedit-dev', + 'texinfo', + 'lcov', + 'libutfcpp-dev', + 'sloccount' + ]) + elif release == 'saucy': + packages.extend([ + 'doxygen', 'cmake', 'ninja-build', 'zlib1g-dev', @@ -609,12 +615,9 @@ class PrepareBuild(CommandLineApp): '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', + ]) + elif release == 'precise': + packages.extend([ 'libtool', 'cmake', 'zlib1g-dev', @@ -628,11 +631,13 @@ class PrepareBuild(CommandLineApp): 'lcov', 'libutfcpp-dev', 'sloccount' - ] + BoostInfo().dependencies('ubuntu-precise') + ]) else: self.log.info('I do not recognize your version of Ubuntu!') packages = None if packages: + packages.extend( + BoostInfo().dependencies('ubuntu-' + release)) self.log.info('Executing: ' + ' '.join(packages)) self.execute(*packages) @@ -692,7 +697,7 @@ class PrepareBuild(CommandLineApp): elif system.startswith('CYGWIN'): self.log.info('Looks like you are using Cygwin') - self.log.info('Please install the dependencies manually.') + self.log.info('Please install the dependencies manually.') ######################################################################### # Determine the system's basic configuration # @@ -1086,7 +1091,7 @@ being 'debug': debug Debugging and --verify support (default) opt Full optimizations gcov Coverage analysis - gprof Code profiling (for OS X, just use: 'shark -i ledger ...') + gprof Code profiling (for macOS, just use: 'shark -i ledger ...') Next is the optional build PHASE, with 'config' being the default: diff --git a/appveyor.yml b/appveyor.yml index 21fec71c..3b2e7267 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,5 +46,5 @@ artifacts: test_script: - C:\msys64\usr\bin\bash -lc "export MINGW_PREFIX=C:/msys64/mingw32/ CTEST_OUTPUT_ON_FAILURE=1 - PATH=/mingw32/bin:$PATH && cd $APPVEYOR_BUILD_FOLDER && + PATH=/mingw32/bin:$PATH && cd $APPVEYOR_BUILD_FOLDER && make test || echo Errors from tests were ignored" diff --git a/cmake/FindUtfcpp.cmake b/cmake/FindUtfcpp.cmake index 185a8d88..93828ef4 100644 --- a/cmake/FindUtfcpp.cmake +++ b/cmake/FindUtfcpp.cmake @@ -10,7 +10,8 @@ set(UTFCPP_FOUND FALSE) find_path(UTFCPP_INCLUDE_DIR NAMES utf8.h - HINTS "${UTFCPP_PATH}" "${PROJECT_SOURCE_DIR}/lib/utfcpp/v2_0/source" + HINTS "${UTFCPP_PATH}" + PATHS "${PROJECT_SOURCE_DIR}/lib/utfcpp/v2_0/source" ) if (UTFCPP_INCLUDE_DIR) diff --git a/contrib/ledger-completion.bash b/contrib/ledger-completion.bash index d46b712d..2da44274 100644 --- a/contrib/ledger-completion.bash +++ b/contrib/ledger-completion.bash @@ -59,7 +59,7 @@ _ledger() accounts="Assets Liabilities Equity Revenue Expenses" case $prev in - --@(cache|file|init-file|output|pager|price-db|script)) + --@(cache|file|init-file|output|pager|price-db|script)|-@(f|i|o)) _filedir return 0 ;; diff --git a/default.nix b/default.nix index 85932da9..a60e607f 100644 --- a/default.nix +++ b/default.nix @@ -26,6 +26,11 @@ stdenv.mkDerivation { cmakeFlags = [ "-DCMAKE_INSTALL_LIBDIR=lib" ]; + buildPhase = "make -j$NIX_BUILD_CORES"; + checkPhase = "ctest -j$NIX_BUILD_CORES"; + + doCheck = true; + meta = { homepage = "http://ledger-cli.org/"; description = "A double-entry accounting system with a command-line reporting interface"; @@ -1,33 +1,72 @@ Ledger NEWS -* 3.1.2 +* 3.1.2 (unreleased) -- Increased maximum length for regex from 255 to 4095 (bug #981). +- Increase maximum length for regex from 255 to 4095 (bug #981) - Initialize periods from from/since clause rather than earliest - transaction date (bug #1159). + transaction date (bug #1159) -- Check balance assertions against the amount after the posting (bug #1147). +- Check balance assertions against the amount after the posting (bug #1147) -- Allow balance assertions with multiple posts to same account (bug #1187). +- Allow balance assertions with multiple posts to same account (bug #1187) -- Fixed period duration of "every X days" and similar statements (bug #370). +- Fix period duration of "every X days" and similar statements (bug #370) -- Option --force-color does not require --color anymore (bug #1109). +- Make option --force-color not require --color anymore (bug #1109) -- Added quoted_rfc4180 to allow CVS output with RFC 4180 compliant quoting. +- Add quoted_rfc4180 to allow CVS output with RFC 4180 compliant quoting. + +- Add support for --prepend-format in accounts command + +- Fix handling of edge cases in trim function (bug #520) + +- Fix auto xact posts not getting applied to account total during + journal parse (bug #552) + +- Transfer null_post flags to generated postings + +- Fix segfault when using --market with --group-by + +- Use amount_width variable for budget report + +- Keep pending items in budgets until the last day they apply + +- Fixed bug where .total used in value expressions breaks totals + +- Make automated transactions work with assertions (bug #1127) + +- Improve parsing of date tokens (bug #1626) + +- Don't attempt to invert a value if it's already zero (bug #1703) + +- Do not parse user-specified init-file twice + +- Fix parsing issue of effective dates (bug #1722, TALOS-2017-0303, + CVE-2017-2807) + +- Fix use-after-free issue with deferred postings (bug #1723, TALOS-2017-0304, + CVE-2017-2808) - Python: Removed double quotes from Unicode values. +- Python: Ensure that parse errors produce useful RuntimeErrors + +- Python: Expose journal expand_aliases + +- Python: Expose journal_t::register_account + +- Improve bash completion + - Emacs Lisp files have been moved to https://github.com/ledger/ledger-mode -- Fixed build under MSYS (32-bit). +- Fix build under MSYS (32-bit). -- Fixed build under Cygwin. +- Fix build under Cygwin. - Various documentation improvements -* 3.1.1 +* 3.1.1 (2016-01-11) - Added a --no-revalued option @@ -57,7 +96,7 @@ - Add continuous integration (https://travis-ci.org/ledger/ledger) -* 3.1 +* 3.1 (2014-10-05) - 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 @@ -154,7 +193,7 @@ features, please see the manual. 2008/07/27 Starting fresh Assets:Checking $750.00 Equity:Opening Balances - + 2008/07/27 Starting fresh Assets:Checking = $1,000.00 Equity:Adjustments @@ -164,7 +203,7 @@ features, please see the manual. 2008/07/27 Starting fresh Assets:Checking $750.00 Equity:Opening Balances - + 2008/07/27 Starting fresh Assets:Checking $250.00 Equity:Adjustments @@ -183,7 +222,7 @@ features, please see the manual. 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 @@ -195,7 +234,7 @@ features, please see the manual. 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 @@ -213,15 +252,15 @@ features, please see the manual. 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 diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 0248844c..9f9a1095 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -427,7 +427,7 @@ accounting problems. Once such tutorial, specifically designed for non-profit charities that seek to use Ledger, can be found at -@url{https://k.sfconservancy.org/npo-ledger-cli} (with a copy on GitHub also +@url{https://k.sfconservancy.org/NPO-Accounting/npo-ledger-cli} (with a copy on GitHub also available at @url{https://github.com/conservancy/npo-ledger-cli/}). If you're looking for information about how to use Ledger's tagging system to handle invoicing, track expenses by program targets, and other such concepts, @@ -1384,10 +1384,11 @@ account: (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: +The use of round brackets creates a virtual posting without ensuring +a balance to zero. 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 @@ -2484,7 +2485,9 @@ Closes block commands like @code{apply} or @code{comment}. @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. +Include the stated file as if it were part of the current file. The file +name can contain a wildcard (@samp{*}) to refer to multiple files (e.g. +@samp{bank/*.ledger}). @item payee @findex payee @@ -2910,7 +2913,7 @@ you a place to put those codes: 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: +@samp{*} after the date, before the code or payee: @smallexample @c input:validate 2012-03-10 * KFC @@ -3101,6 +3104,13 @@ it appears as: This shows that they are all in the same transaction (which is why the date is not repeated), but they have different payees now. +If using the @option{--strict} or @option{--pedantic} options, you must +declare this tag to avoid warnings and errors. + +The payee name used with the tag is not enforced by the +@option{--check-payees} option, due to a bug: +@url{https://github.com/ledger/ledger/issues/556}. + @node Metadata values, Typed metadata, Metadata tags, Metadata @subsection Metadata values @@ -3185,9 +3195,9 @@ look ``harder'') rather than parentheses: @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: +An amount is a numerical figure with a 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 @@ -3228,6 +3238,45 @@ A balance assertion has this general form: This simply asserts that after subtracting $20.00 from Assets:Cash, that the resulting total matches $500.00. If not, it is an error. +The assertion has an effect only on the specified commodity. If an account has +multiple commodities, then only the one asserted is verified: + +@smallexample +2012-03-10 KFC New York + Expenses:Food $20.00 + Assets:Cash $-20.00 = $500.00 + +2012-03-11 KFC Montreal + Expenses:Food 15.00 CAD + Assets:Cash -15.00 CAD = $500.00 +@end smallexample + +In this case, the amount in USD of cash (which has not changed) is validated. +Nothing is asserted about the current amount of Canadian dollars in @samp{Asset:Cash}. + +@subsubsection Special assertion value 0 + +The only value that can be asserted without a commodity is @samp{0}. +This results in a cross-commodities assertion, which makes it possible to +assert that an account is totally empty. + +@smallexample +2012-03-09 Fill Wallet + Revenue $20.00 + Revenue 15.00 CAD + Assets:Cash + +2012-03-10 KFC New York + Expenses:Food $20.00 + Assets:Cash $-20.00 + +2012-03-11 KFC Montreal + Expenses:Food 15.00 CAD + Assets:Cash -15.00 CAD = 0 +@end smallexample + +The last transaction will assert that we are out of cash of any sort. + @node Balance assignments, Resetting a balance, Balance assertions, Balance verification @subsection Balance assignments @@ -3504,12 +3553,12 @@ Plus, it comes with dangers. This works fine: 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{$50.00@} @@ $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 + Assets:Brokerage -5 AAPL @{$50.00@} @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -3523,12 +3572,12 @@ But this does not do what you might expect: 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@ $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 + Assets:Brokerage -5 AAPL @{@{$500.00@}@} @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -3604,7 +3653,7 @@ expressions): @smallexample 2012-04-10 My Broker Assets:Brokerage:Cash $375.00 - Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] @@ $375.00 + Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -3621,7 +3670,7 @@ 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 + Assets:Brokerage -5 AAPL @{$50.00@} [2012-04-10] (Oh my!) @@@@ $375.00 Income:Capital Gains $-125.00 @end smallexample @@ -8087,8 +8136,13 @@ posting. 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. +@itemx expr has_tag(/REGEX/) +@itemx expr has_tag('TAG') +A regular expression (REGEX) or string (TAG) that checks for the tags of +a transaction. + +@item tag(REGEX) =~ /REGEX/ +A regular expression that matches a transaction's tags against its values. @item expr date =~ /REGEX/ Useful for specifying a date in plain terms. For example, you could say @@ -8157,6 +8211,19 @@ posting. A regular expression that matches against the transaction code (the text that occurs between parentheses before the payee). +@item expr any(KEYWORD =~ /REGEX/) +The @command{any} keyword is used to specify that at least one posting of +the transaction must match the expression in brackets. For example, +@samp{ledger -f d reg expr "any(account =~ /Assets:/)"} can be used to +display all transactions which involve at least one @samp{Assets:} +account. + +@item expr all(KEYWORD =~ /REGEX/) +The @command{all} keyword is used to specify that all postings of a +transactions must match the expression in brackets. For example, +@samp{ledger -f d reg expr "all(account =~ /Assets:/)"} can be used to +display all transactions where all accounts are @samp{Assets:}. + @end table The @command{query} command can be used to see how Ledger interprets diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71d9478a..9cd54dbe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,7 +52,6 @@ set(LEDGER_SOURCES times.cc error.cc utils.cc - strptime.cc wcwidth.cc) if (HAVE_BOOST_PYTHON) @@ -134,9 +133,16 @@ set(LEDGER_INCLUDES value.h views.h xact.h - strptime.h ${PROJECT_BINARY_DIR}/system.hh) +# Windows provides no strptime(), so supply our own. +if (WIN32 OR CYGWIN) + list(APPEND LEDGER_INCLUDES + strptime.h) + list(APPEND LEDGER_SOURCES + strptime.cc) +endif() + if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_CXX_COMPILER MATCHES "clang\\+\\+") add_definitions( diff --git a/src/amount.cc b/src/amount.cc index 05145f87..c6463b2b 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -604,7 +604,9 @@ void amount_t::in_place_invert() throw_(amount_error, _("Cannot invert an uninitialized amount")); _dup(); - mpq_inv(MP(quantity), MP(quantity)); + + if (sign() != 0) + mpq_inv(MP(quantity), MP(quantity)); } void amount_t::in_place_round() diff --git a/src/annotate.cc b/src/annotate.cc index ab81d412..c5ccdf07 100644 --- a/src/annotate.cc +++ b/src/annotate.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -81,7 +81,7 @@ bool annotation_t::operator<(const annotation_t& rhs) const void annotation_t::parse(std::istream& in) { do { - istream_pos_type pos = in.tellg(); + std::istream::pos_type pos = in.tellg(); if (static_cast<int>(pos) < 0) return; diff --git a/src/commodity.cc b/src/commodity.cc index a8520ca1..d6d5ca98 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -294,7 +294,7 @@ bool commodity_t::symbol_needs_quotes(const string& symbol) void commodity_t::parse_symbol(std::istream& in, string& symbol) { - istream_pos_type pos = in.tellg(); + std::istream::pos_type pos = in.tellg(); char buf[256]; char c = peek_next_nonws(in); diff --git a/src/context.h b/src/context.h index 4a7a5441..ca7af060 100644 --- a/src/context.h +++ b/src/context.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -58,18 +58,19 @@ public: shared_ptr<std::istream> 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; + path pathname; + path current_directory; + journal_t * journal; + account_t * master; + scope_t * scope; + char linebuf[MAX_LINE + 1]; + std::istream::pos_type line_beg_pos; + std::istream::pos_type curr_pos; + std::size_t linenum; + std::size_t errors; + std::size_t count; + std::size_t sequence; + std::string last; explicit parse_context_t(const path& cwd) : current_directory(cwd), master(NULL), scope(NULL), @@ -112,11 +113,7 @@ 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); diff --git a/src/draft.cc b/src/draft.cc index 424b7a9a..12d7faf7 100644 --- a/src/draft.cc +++ b/src/draft.cc @@ -68,7 +68,7 @@ void draft_t::xact_template_t::dump(std::ostream& out) const } else { foreach (const post_template_t& post, posts) { out << std::endl - << _f("[Posting \"%1\"]") % (post.from ? _("from") : _("to")) + << _f("[Posting \"%1%\"]") % (post.from ? _("from") : _("to")) << std::endl; if (post.account_mask) diff --git a/src/error.cc b/src/error.cc index 7e49645b..1ab92840 100644 --- a/src/error.cc +++ b/src/error.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -78,10 +78,10 @@ string line_context(const string& line, return buf.str(); } -string source_context(const path& file, - const istream_pos_type pos, - const istream_pos_type end_pos, - const string& prefix) +string source_context(const path& file, + const std::istream::pos_type pos, + const std::istream::pos_type end_pos, + const string& prefix) { const std::streamoff len = end_pos - pos; if (! len || file.empty()) diff --git a/src/error.h b/src/error.h index 88e09329..3bdcde98 100644 --- a/src/error.h +++ b/src/error.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -47,7 +47,7 @@ namespace ledger { extern std::ostringstream _desc_buffer; template <typename T> -inline void throw_func(const string& message) { +[[ noreturn ]] inline void throw_func(const string& message) { _desc_buffer.clear(); _desc_buffer.str(""); throw T(message); @@ -81,10 +81,10 @@ 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 = ""); +string source_context(const path& file, + const std::istream::pos_type pos, + const std::istream::pos_type end_pos, + const string& prefix = ""); #define DECLARE_EXCEPTION(name, kind) \ class name : public kind { \ @@ -95,8 +95,9 @@ string source_context(const path& file, struct error_count { std::size_t count; - explicit error_count(std::size_t _count) : count(_count) {} - const char * what() const { return ""; } + std::string message; + explicit error_count(std::size_t _count, std::string _msg) : count(_count), message(_msg) {} + const char * what() const { return message.c_str(); } }; } // namespace ledger diff --git a/src/expr.cc b/src/expr.cc index 85818e4b..c8945d3d 100644 --- a/src/expr.cc +++ b/src/expr.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -94,9 +94,9 @@ void expr_t::parse(std::istream& in, const parse_flags_t& flags, const optional<string>& original_string) { parser_t parser; - istream_pos_type start_pos = in.tellg(); + std::istream::pos_type start_pos = in.tellg(); ptr = parser.parse(in, flags, original_string); - istream_pos_type end_pos = in.tellg(); + std::istream::pos_type end_pos = in.tellg(); if (original_string) { set_text(*original_string); @@ -290,10 +290,10 @@ value_t source_command(call_scope_t& args) in = &std::cin; } - symbol_scope_t file_locals(args); - std::size_t linenum = 0; - char buf[4096]; - istream_pos_type pos; + symbol_scope_t file_locals(args); + std::size_t linenum = 0; + char buf[4096]; + std::istream::pos_type pos; while (in->good() && ! in->eof()) { pos = in->tellg(); diff --git a/src/format.cc b/src/format.cc index bb578141..5b9baa21 100644 --- a/src/format.cc +++ b/src/format.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -103,7 +103,7 @@ namespace { p += std::strlen(p); } else { assert(str.good()); - istream_pos_type pos = str.tellg(); + std::istream::pos_type pos = str.tellg(); expr.set_text(string(p, p + long(pos))); p += long(pos) - 1; diff --git a/src/global.h b/src/global.h index 01ad60cc..8f8266ac 100644 --- a/src/global.h +++ b/src/global.h @@ -166,7 +166,7 @@ See LICENSE file included with the distribution for details and disclaimer."); OPTION_(global_scope_t, version, DO() { // -v parent->show_version_info(std::cout); - throw error_count(0); // exit immediately + throw error_count(0, ""); // exit immediately }); }; diff --git a/src/item.cc b/src/item.cc index bd025c52..7132103e 100644 --- a/src/item.cc +++ b/src/item.cc @@ -152,7 +152,7 @@ void item_t::parse_tags(const char * p, if (const char * b = std::strchr(p, '[')) { if (*(b + 1) != '\0' && (std::isdigit(*(b + 1)) || *(b + 1) == '=')) { - if (const char * e = std::strchr(p, ']')) { + if (const char * e = std::strchr(b, ']')) { char buf[256]; std::strncpy(buf, b + 1, static_cast<std::size_t>(e - b - 1)); buf[e - b - 1] = '\0'; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -48,12 +48,12 @@ namespace ledger { struct position_t { - path pathname; - istream_pos_type beg_pos; - std::size_t beg_line; - istream_pos_type end_pos; - std::size_t end_line; - std::size_t sequence; + path pathname; + std::istream::pos_type beg_pos; + std::size_t beg_line; + std::istream::pos_type end_pos; + std::size_t end_line; + std::size_t sequence; position_t() : beg_pos(0), beg_line(0), end_pos(0), end_line(0), sequence(0) { diff --git a/src/main.cc b/src/main.cc index c8b9ec3a..218579cf 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,6 +35,10 @@ // was moved there for the sake of clarity here #include "session.h" +#ifdef HAVE_EDIT +#include <editline/readline.h> +#endif + using namespace ledger; #if HAVE_BOOST_PYTHON @@ -69,9 +73,6 @@ int main(int argc, char * argv[], char * envp[]) // Initialize global Boost/C++ environment std::ios::sync_with_stdio(false); -#if BOOST_VERSION < 104600 - filesystem::path::default_name_check(filesystem::portable_posix_name); -#endif std::signal(SIGINT, sigint_handler); #if !defined(_WIN32) && !defined(__CYGWIN__) @@ -128,13 +129,9 @@ int main(int argc, char * argv[], char * envp[]) bool exit_loop = false; -#if HAVE_EDIT - +#ifdef HAVE_EDIT rl_readline_name = const_cast<char *>("Ledger"); -#if 0 - // jww (2009-02-05): NYI - rl_attempted_completion_function = ledger_completion; -#endif + // TODO: rl_attempted_completion_function = ledger_completion; while (char * p = readline(global_scope->prompt_string())) { char * expansion = NULL; @@ -173,7 +170,7 @@ int main(int argc, char * argv[], char * envp[]) global_scope->execute_command_wrapper(split_arguments(p), true); } -#if HAVE_EDIT +#ifdef HAVE_EDIT if (expansion) std::free(expansion); std::free(p); diff --git a/src/mask.cc b/src/mask.cc index 434acad6..35e690de 100644 --- a/src/mask.cc +++ b/src/mask.cc @@ -75,9 +75,8 @@ mask_t& mask_t::assign_glob(const string& pat) if (i + 1 < len) { re_pat += pat[++i]; break; - } else { - // fallthrough... } + // fallthrough... default: re_pat += pat[i]; break; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -963,7 +963,7 @@ void expr_t::op_t::dump(std::ostream& out, const int depth) const string op_context(const expr_t::ptr_op_t op, const expr_t::ptr_op_t locus) { - ostream_pos_type start_pos, end_pos; + std::ostream::pos_type start_pos, end_pos; expr_t::op_t::context_t context(op, locus, &start_pos, &end_pos); std::ostringstream buf; buf << " "; @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -285,19 +285,19 @@ public: struct context_t { - ptr_op_t expr_op; - ptr_op_t op_to_find; - ostream_pos_type * start_pos; - ostream_pos_type * end_pos; - bool relaxed; + ptr_op_t expr_op; + ptr_op_t op_to_find; + std::ostream::pos_type * start_pos; + std::ostream::pos_type * end_pos; + bool relaxed; context_t() : start_pos(NULL), end_pos(NULL), relaxed(false) {} - context_t(const ptr_op_t& _expr_op, - const ptr_op_t& _op_to_find, - ostream_pos_type * const _start_pos = NULL, - ostream_pos_type * const _end_pos = NULL, - const bool _relaxed = true) + context_t(const ptr_op_t& _expr_op, + const ptr_op_t& _op_to_find, + std::ostream::pos_type * const _start_pos = NULL, + std::ostream::pos_type * const _end_pos = NULL, + const bool _relaxed = true) : expr_op(_expr_op), op_to_find(_op_to_find), start_pos(_start_pos), end_pos(_end_pos), relaxed(_relaxed) {} diff --git a/src/output.cc b/src/output.cc index c2fa83ac..09d3ad9e 100644 --- a/src/output.cc +++ b/src/output.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -284,8 +284,9 @@ void report_accounts::flush() std::ostream& out(report.output_stream); format_t prepend_format; std::size_t prepend_width; + bool do_prepend_format; - if (report.HANDLED(prepend_format_)) { + if ((do_prepend_format = report.HANDLED(prepend_format_))) { prepend_format.parse_format(report.HANDLER(prepend_format_).str()); prepend_width = report.HANDLED(prepend_width_) ? lexical_cast<std::size_t>(report.HANDLER(prepend_width_).str()) @@ -293,7 +294,7 @@ void report_accounts::flush() } foreach (accounts_pair& entry, accounts) { - if (prepend_format) { + if (do_prepend_format) { bind_scope_t bound_scope(report, *entry.first); out.width(static_cast<std::streamsize>(prepend_width)); out << prepend_format(bound_scope); diff --git a/src/py_post.cc b/src/py_post.cc index 6195570b..525b47c2 100644 --- a/src/py_post.cc +++ b/src/py_post.cc @@ -153,6 +153,11 @@ void export_post() return_value_policy<return_by_value>()), make_setter(&post_t::cost, return_value_policy<return_by_value>())) + .add_property("given_cost", + make_getter(&post_t::given_cost, + return_value_policy<return_by_value>()), + make_setter(&post_t::given_cost, + return_value_policy<return_by_value>())) .add_property("assigned_amount", make_getter(&post_t::assigned_amount, return_value_policy<return_by_value>()), diff --git a/src/py_value.cc b/src/py_value.cc index f4f63946..486228c0 100644 --- a/src/py_value.cc +++ b/src/py_value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -88,12 +88,6 @@ namespace { return buf.str(); } - string py_dump_relaxed(const value_t& value) { - std::ostringstream buf; - value.dump(buf, true); - return buf.str(); - } - void py_set_string(value_t& value, const string& str) { return value.set_string(str); } diff --git a/src/pyinterp.cc b/src/pyinterp.cc index 19430bef..fad0b559 100644 --- a/src/pyinterp.cc +++ b/src/pyinterp.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -222,27 +222,13 @@ object python_interpreter_t::import_option(const string& str) python::list paths(sys_dict["path"]); if (contains(str, ".py")) { -#if BOOST_VERSION >= 103700 path& cwd(parsing_context.get_current().current_directory); -#if BOOST_VERSION >= 104600 && BOOST_FILESYSTEM_VERSION >= 3 path parent(filesystem::absolute(file, cwd).parent_path()); -#else - path parent(filesystem::complete(file, cwd).parent_path()); -#endif DEBUG("python.interp", "Adding " << parent << " to PYTHONPATH"); paths.insert(0, parent.string()); sys_dict["path"] = paths; -#if BOOST_VERSION >= 104600 name = file.stem().string(); -#else - name = file.stem(); -#endif -#else // BOOST_VERSION >= 103700 - paths.insert(0, file.branch_path().string()); - sys_dict["path"] = paths; - name = file.leaf(); -#endif // BOOST_VERSION >= 103700 } try { diff --git a/src/query.cc b/src/query.cc index fc1d4ff0..883bea40 100644 --- a/src/query.cc +++ b/src/query.cc @@ -155,6 +155,7 @@ query_t::lexer_t::next_token(query_t::lexer_t::token_t::kind_t tok_context) case ')': if (! consume_next && tok_context == token_t::TOK_EXPR) goto test_ident; + // fall through... case '(': case '&': case '|': diff --git a/src/quotes.cc b/src/quotes.cc index 50791560..8ae8ca90 100644 --- a/src/quotes.cc +++ b/src/quotes.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -79,13 +79,8 @@ commodity_quote_from_script(commodity_t& commodity, if (optional<std::pair<commodity_t *, price_point_t> > point = commodity_pool_t::current_pool->parse_price_directive(buf)) { if (commodity_pool_t::current_pool->price_db) { -#if defined(__GNUG__) && __GNUG__ < 3 - ofstream database(*commodity_pool_t::current_pool->price_db, - ios::out | ios::app); -#else ofstream database(*commodity_pool_t::current_pool->price_db, std::ios_base::out | std::ios_base::app); -#endif database << "P " << format_datetime(point->second.when, FMT_WRITTEN) << " " << commodity.symbol() diff --git a/src/session.cc b/src/session.cc index e95123e8..427850d9 100644 --- a/src/session.cc +++ b/src/session.cc @@ -76,7 +76,7 @@ std::size_t session_t::read_data(const string& master_account) file = path(home_var) / ".ledger"; if (! file.empty() && exists(file)) - HANDLER(file_).data_files.insert(file); + HANDLER(file_).data_files.push_back(file); else throw_(parse_error, "No journal file was specified (please use -f)"); @@ -214,7 +214,7 @@ journal_t * session_t::read_journal_files() journal_t * session_t::read_journal(const path& pathname) { HANDLER(file_).data_files.clear(); - HANDLER(file_).data_files.insert(pathname); + HANDLER(file_).data_files.push_back(pathname); return read_journal_files(); } diff --git a/src/session.h b/src/session.h index 47732a3d..4dce3816 100644 --- a/src/session.h +++ b/src/session.h @@ -153,14 +153,14 @@ public: OPTION__ (session_t, file_, // -f - std::set<path COMMA ComparePaths> data_files; + std::list<path> data_files; CTOR(session_t, file_) {} DO_(str) { if (parent->flush_on_next_data_file) { data_files.clear(); parent->flush_on_next_data_file = false; } - data_files.insert(str); + data_files.push_back(str); }); OPTION_(session_t, input_date_format_, DO_(str) { diff --git a/src/stream.cc b/src/stream.cc index c4bbb42e..91a190d8 100644 --- a/src/stream.cc +++ b/src/stream.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -89,11 +89,7 @@ namespace { else { // parent close(pfd[0]); typedef iostreams::stream<iostreams::file_descriptor_sink> fdstream; -#if BOOST_VERSION >= 104400 *os = new fdstream(pfd[1], iostreams::never_close_handle); -#else // BOOST_VERSION >= 104400 - *os = new fdstream(pfd[1]); -#endif // BOOST_VERSION >= 104400 } return pfd[1]; #else diff --git a/src/strptime.cc b/src/strptime.cc index 069b9267..ac6885a6 100644 --- a/src/strptime.cc +++ b/src/strptime.cc @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(_WIN32) || defined(__CYGWIN__) +#if !(defined(_WIN32) || defined(__CYGWIN__)) +#error This file should only be compiled on Windows. +#endif + // Implement strptime under windows #include "strptime.h" @@ -200,5 +203,3 @@ static char* _strptime(const char *s, const char *format, struct tm *tm) { char* strptime(const char *buf, const char *fmt, struct tm *tm) { return _strptime(buf, fmt, tm); } - -#endif // _WIN32 diff --git a/src/system.hh.in b/src/system.hh.in index 38ac1e63..97651e8b 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -57,11 +57,9 @@ #define Ledger_VERSION_PRERELEASE "@Ledger_VERSION_PRERELEASE@" #define Ledger_VERSION_DATE @Ledger_VERSION_DATE@ -#define HAVE_EDIT @HAVE_EDIT@ #define HAVE_GETTEXT @HAVE_GETTEXT@ -#cmakedefine HAVE_ACCESS -#cmakedefine HAVE_REALPATH +#cmakedefine HAVE_EDIT #cmakedefine HAVE_GETPWUID #cmakedefine HAVE_GETPWNAM #cmakedefine HAVE_IOCTL @@ -83,14 +81,9 @@ /* System includes */ /*------------------------------------------------------------------------*/ -#if defined(__GNUG__) && __GNUG__ < 3 -#define _XOPEN_SOURCE -#endif - #include <algorithm> #include <exception> #include <typeinfo> -#include <locale> #include <stdexcept> #include <iostream> #include <streambuf> @@ -107,29 +100,6 @@ #include <string> #include <vector> -#if defined(__GNUG__) && __GNUG__ < 3 - -namespace std { - inline ostream & right (ostream & i) { - i.setf(i.right, i.adjustfield); - return i; - } - inline ostream & left (ostream & i) { - i.setf(i.left, i.adjustfield); - return i; - } -} - -typedef std::streamoff istream_pos_type; -typedef std::streamoff ostream_pos_type; - -#else // ! (defined(__GNUG__) && __GNUG__ < 3) - -typedef std::istream::pos_type istream_pos_type; -typedef std::ostream::pos_type ostream_pos_type; - -#endif - #include <cassert> #include <cctype> #include <cstdarg> @@ -138,12 +108,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include <cstring> #include <csignal> -#if defined __FreeBSD__ && __FreeBSD__ <= 4 -// FreeBSD has a broken isspace macro, so don't use it -#undef isspace(c) -#endif - -#include <sys/stat.h> #if defined(_WIN32) || defined(__CYGWIN__) #include <io.h> #else @@ -167,10 +131,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include <mpfr.h> #include "utf8.h" -#if HAVE_EDIT -#include <editline/readline.h> -#endif - #include <boost/algorithm/string.hpp> #include <boost/any.hpp> #include <boost/bind.hpp> @@ -247,7 +207,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include <boost/python/detail/wrap_python.hpp> #include <datetime.h> -#include <unicodeobject.h> #include <boost/python/module_init.hpp> #include <boost/python/suite/indexing/vector_indexing_suite.hpp> diff --git a/src/textual.cc b/src/textual.cc index 8fbc5c08..04e90e45 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -282,6 +282,10 @@ void instance_t::parse() std::cerr << _("Error: ") << err.what() << std::endl; context.errors++; + if (! current_context.empty()) + context.last = current_context + "\n" + err.what(); + else + context.last = err.what(); } } @@ -553,7 +557,7 @@ void instance_t::option_directive(char * line) void instance_t::automated_xact_directive(char * line) { - istream_pos_type pos = context.line_beg_pos; + std::istream::pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -649,7 +653,7 @@ void instance_t::automated_xact_directive(char * line) void instance_t::period_xact_directive(char * line) { - istream_pos_type pos = context.line_beg_pos; + std::istream::pos_type pos = context.line_beg_pos; bool reveal_context = true; @@ -739,17 +743,8 @@ void instance_t::include_directive(char * line) DEBUG("textual.include", "resolved path: " << filename.string()); mask_t glob; -#if BOOST_VERSION >= 103700 path parent_path = filename.parent_path(); -#if BOOST_VERSION >= 104600 glob.assign_glob('^' + filename.filename().string() + '$'); -#else - glob.assign_glob('^' + filename.filename() + '$'); -#endif -#else // BOOST_VERSION >= 103700 - path parent_path = filename.branch_path(); - glob.assign_glob('^' + filename.leaf() + '$'); -#endif // BOOST_VERSION >= 103700 bool files_found = false; if (exists(parent_path)) { @@ -757,21 +752,9 @@ void instance_t::include_directive(char * line) for (filesystem::directory_iterator iter(parent_path); iter != end; ++iter) { -#if BOOST_VERSION <= 103500 - if (is_regular(*iter)) -#else if (is_regular_file(*iter)) -#endif { -#if BOOST_VERSION >= 103700 -#if BOOST_VERSION >= 104600 string base = (*iter).path().filename().string(); -#else - string base = (*iter).filename(); -#endif -#else // BOOST_VERSION >= 103700 - string base = (*iter).leaf(); -#endif // BOOST_VERSION >= 103700 if (glob.match(base)) { journal_t * journal = context.journal; account_t * master = top_account(); @@ -912,8 +895,8 @@ void instance_t::end_apply_directive(char * kind) void instance_t::account_directive(char * line) { - istream_pos_type beg_pos = context.line_beg_pos; - std::size_t beg_linenum = context.linenum; + std::istream::pos_type beg_pos = context.line_beg_pos; + std::size_t beg_linenum = context.linenum; char * p = skip_ws(line); account_t * account = @@ -1644,29 +1627,30 @@ post_t * instance_t::parse_post(char * line, } DEBUG("textual.parse", "line " << context.linenum << ": " - << "POST assign: parsed amt = " << *post->assigned_amount); + << "POST assign: parsed balance amount = " << *post->assigned_amount); - amount_t& amt(*post->assigned_amount); + const amount_t& amt(*post->assigned_amount); value_t account_total (post->account->amount().strip_annotations(keep_details_t())); DEBUG("post.assign", "line " << context.linenum << ": " << "account balance = " << account_total); - DEBUG("post.assign", - "line " << context.linenum << ": " << "post amount = " << amt); + DEBUG("post.assign", "line " << context.linenum << ": " + << "post amount = " << amt << " (is_zero = " << amt.is_zero() << ")"); - amount_t diff = amt; + balance_t diff = amt; switch (account_total.type()) { case value_t::AMOUNT: - if (account_total.as_amount().commodity_ptr() == diff.commodity_ptr()) - diff -= account_total.as_amount(); + diff -= account_total.as_amount(); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Subtracting amount " << account_total.as_amount() << " from diff, yielding " << diff); break; case value_t::BALANCE: - if (optional<amount_t> comm_bal = - account_total.as_balance().commodity_amount(amt.commodity())) - diff -= *comm_bal; + diff -= account_total.as_balance(); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Subtracting balance " << account_total.as_balance() << " from diff, yielding " << diff); break; default: @@ -1680,18 +1664,34 @@ post_t * instance_t::parse_post(char * line, // Subtract amounts from previous posts to this account in the xact. for (post_t* p : xact->posts) { - if (p->account == post->account && - p->amount.commodity_ptr() == diff.commodity_ptr()) { + if (p->account == post->account) { diff -= p->amount; DEBUG("textual.parse", "line " << context.linenum << ": " - << "Subtract " << p->amount << ", diff = " << diff); + << "Subtracting " << p->amount << ", diff = " << diff); } } + // If amt has a commodity, restrict balancing to that. Otherwise, it's the blanket '0' and + // check that all of them are zero. + if (amt.has_commodity()) { + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Finding commodity " << amt.commodity() << " (" << amt << ") in balance " << diff); + optional<amount_t> wanted_commodity = diff.commodity_amount(amt.commodity()); + if (!wanted_commodity) { + diff = amt - amt; // this is '0' with the correct commodity. + } else { + diff = *wanted_commodity; + } + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Diff is now " << diff); + } + if (post->amount.is_null()) { // balance assignment if (! diff.is_zero()) { - post->amount = diff; + // This will fail if there are more than 1 commodity in diff, which is wanted, + // as amount cannot store more than 1 commodity. + post->amount = diff.to_amount(); DEBUG("textual.parse", "line " << context.linenum << ": " << "Overwrite null posting"); } @@ -1699,10 +1699,11 @@ post_t * instance_t::parse_post(char * line, // balance assertion diff -= post->amount; if (! no_assertions && ! diff.is_zero()) { - amount_t tot = amt - diff; + balance_t tot = -diff + amt; + DEBUG("textual.parse", "Balance assertion: off by " << diff << " (expected to see " << tot << ")"); throw_(parse_error, _f("Balance assertion off by %1% (expected to see %2%)") - % diff % tot); + % diff.to_string() % tot.to_string()); } } @@ -2012,7 +2013,8 @@ std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) TRACE_FINISH(parsing_total, 1); if (context_stack.get_current().errors > 0) - throw error_count(context_stack.get_current().errors); + throw error_count(context_stack.get_current().errors, + context_stack.get_current().last); return context_stack.get_current().count; } diff --git a/src/times.cc b/src/times.cc index eda71ae7..db0d74ff 100644 --- a/src/times.cc +++ b/src/times.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -43,23 +43,11 @@ optional<datetime_t> epoch; date_time::weekdays start_of_week = gregorian::Sunday; -//#define USE_BOOST_FACETS 1 -#if defined(USE_BOOST_FACETS) -#error "Boost facets are not quite working yet" -#endif - namespace { template <typename T, typename InputFacetType, typename OutputFacetType> class temporal_io_t : public noncopyable { - string fmt_str; -#if defined(USE_BOOST_FACETS) - std::istringstream input_stream; - std::ostringstream output_stream; - InputFacetType * input_facet; - OutputFacetType * output_facet; - std::string temp_string; -#endif // USE_BOOST_FACETS + string fmt_str; public: date_traits_t traits; @@ -71,15 +59,6 @@ namespace { icontains(fmt_str, "%m") || icontains(fmt_str, "%b"), icontains(fmt_str, "%d")), input(_input) { -#if defined(USE_BOOST_FACETS) - if (input) { - input_facet = new InputFacetType(fmt_str); - input_stream.imbue(std::locale(std::locale::classic(), input_facet)); - } else { - output_facet = new OutputFacetType(fmt_str); - output_stream.imbue(std::locale(std::locale::classic(), output_facet)); - } -#endif // USE_BOOST_FACETS } void set_format(const char * fmt) { @@ -88,29 +67,15 @@ namespace { icontains(fmt_str, "%m") || icontains(fmt_str, "%b"), icontains(fmt_str, "%d")); -#if defined(USE_BOOST_FACETS) - if (input) - input_facet->format(fmt_str); - else - output_facet->format(fmt_str); -#endif // USE_BOOST_FACETS } T parse(const char *) {} std::string format(const T& when) { -#if defined(USE_BOOST_FACETS) - output_stream.str(temp_string); - output_stream.seekp(std::ios_base::beg); - output_stream.clear(); - output_stream << when; - return output_stream.str(); -#else // USE_BOOST_FACETS std::tm data(to_tm(when)); char buf[128]; std::strftime(buf, 127, fmt_str.c_str(), &data); return buf; -#endif // USE_BOOST_FACETS } }; @@ -119,34 +84,12 @@ namespace { posix_time::time_facet> ::parse(const char * str) { -#if defined(USE_BOOST_FACETS) - input_stream.seekg(std::ios_base::beg); - input_stream.clear(); - input_stream.str(str); - - datetime_t when; - input_stream >> when; -#if DEBUG_ON - if (when.is_not_a_date_time()) - DEBUG("times.parse", "Failed to parse date/time '" << str - << "' using pattern '" << fmt_str << "'"); -#endif - - if (! when.is_not_a_date_time() && - input_stream.good() && ! input_stream.eof() && - input_stream.peek() != EOF) { - DEBUG("times.parse", "This string has leftovers: '" << str << "'"); - return datetime_t(); - } - return when; -#else // USE_BOOST_FACETS std::tm data; std::memset(&data, 0, sizeof(std::tm)); if (strptime(str, fmt_str.c_str(), &data)) return posix_time::ptime_from_tm(data); else return datetime_t(); -#endif // USE_BOOST_FACETS } template <> @@ -154,27 +97,6 @@ namespace { gregorian::date_facet> ::parse(const char * str) { -#if defined(USE_BOOST_FACETS) - input_stream.seekg(std::ios_base::beg); - input_stream.clear(); - input_stream.str(str); - - date_t when; - input_stream >> when; -#if DEBUG_ON - if (when.is_not_a_date()) - DEBUG("times.parse", "Failed to parse date '" << str - << "' using pattern '" << fmt_str << "'"); -#endif - - if (! when.is_not_a_date() && - input_stream.good() && ! input_stream.eof() && - input_stream.peek() != EOF) { - DEBUG("times.parse", "This string has leftovers: '" << str << "'"); - return date_t(); - } - return when; -#else // USE_BOOST_FACETS std::tm data; std::memset(&data, 0, sizeof(std::tm)); data.tm_year = CURRENT_DATE().year() - 1900; @@ -183,7 +105,6 @@ namespace { return gregorian::date_from_tm(data); else return date_t(); -#endif // USE_BOOST_FACETS } typedef temporal_io_t<datetime_t, posix_time::time_input_facet, diff --git a/src/token.cc b/src/token.cc index 1ec052ed..76bf5106 100644 --- a/src/token.cc +++ b/src/token.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -380,7 +380,7 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) break; default: { - istream_pos_type pos = in.tellg(); + std::istream::pos_type pos = in.tellg(); // First, check to see if it's a reserved word, such as: and or not int result = parse_reserved_word(in); diff --git a/src/utils.h b/src/utils.h index b21dff7a..c9146dd7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -44,7 +44,11 @@ #ifndef _UTILS_H #define _UTILS_H +#if (BOOST_VERSION >= 106600) +#include <boost/uuid/detail/sha1.hpp> +#else #include <boost/uuid/sha1.hpp> +#endif /** * @name Default values @@ -496,10 +500,6 @@ inline T& downcast(U& object) { path resolve_path(const path& pathname); -#ifdef HAVE_REALPATH -extern "C" char * realpath(const char *, char resolved_path[]); -#endif - inline const string& either_or(const string& first, const string& second) { return first.empty() ? second : first; diff --git a/src/value.cc b/src/value.cc index 1841505d..5ce30530 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -129,11 +129,7 @@ value_t::operator bool() const void value_t::set_type(type_t new_type) { if (new_type == VOID) { -#if BOOST_VERSION >= 103700 storage.reset(); -#else - storage = intrusive_ptr<storage_t>(); -#endif } else { if (! storage || storage->refc > 1) storage = new storage_t; diff --git a/src/value.h b/src/value.h index 35581bfb..e44f9354 100644 --- a/src/value.h +++ b/src/value.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2018, John Wiegley. All rights reserved. + * Copyright (c) 2003-2019, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -878,22 +878,14 @@ public: VERIFY(! is_null()); if (! is_sequence()) { -#if BOOST_VERSION >= 103700 storage.reset(); -#else - storage = intrusive_ptr<storage_t>(); -#endif } else { as_sequence_lval().pop_back(); const sequence_t& seq(as_sequence()); std::size_t new_size = seq.size(); if (new_size == 0) { -#if BOOST_VERSION >= 103700 storage.reset(); -#else - storage = intrusive_ptr<storage_t>(); -#endif } else if (new_size == 1) { *this = seq.front(); diff --git a/src/xact.cc b/src/xact.cc index 5df9ebc5..10a7106a 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -395,10 +395,12 @@ bool xact_base_t::finalize() some_null = true; } - if (post->has_flags(POST_DEFERRED)) - post->account->add_deferred_post(id(), post); - else + if (post->has_flags(POST_DEFERRED)) { + if (!post->amount.is_null()) + post->account->add_deferred_post(id(), post); + } else { post->account->add_post(post); + } post->xdata().add_flags(POST_EXT_VISITED); post->account->xdata().add_flags(ACCOUNT_EXT_VISITED); @@ -115,7 +115,7 @@ public: virtual string description() { if (pos) { std::ostringstream buf; - buf << _f("transaction at line %1") << pos->beg_line; + buf << _f("transaction at line %1%") % pos->beg_line; return buf.str(); } else { return string(_("generated transaction")); @@ -177,7 +177,7 @@ public: virtual string description() { if (pos) { std::ostringstream buf; - buf << _f("automated transaction at line %1") << pos->beg_line; + buf << _f("automated transaction at line %1%") % pos->beg_line; return buf.str(); } else { return string(_("generated automated transaction")); @@ -220,7 +220,7 @@ class period_xact_t : public xact_base_t virtual string description() { if (pos) { std::ostringstream buf; - buf << _f("periodic transaction at line %1") << pos->beg_line; + buf << _f("periodic transaction at line %1%") % pos->beg_line; return buf.str(); } else { return string(_("generated periodic transaction")); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d0b62f6e..41eecb36 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,8 +27,6 @@ macro(add_ledger_harness_tests _class) ${TestFile} ${TEST_PYTHON_FLAGS}) set_tests_properties(${_class}Test_${TestFile_Name} PROPERTIES ENVIRONMENT "TZ=${Ledger_TEST_TIMEZONE}") - set_target_properties(check - PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) endif() endforeach() endif() @@ -48,8 +46,6 @@ if (PYTHONINTERP_FOUND) --ledger $<TARGET_FILE:ledger> --file ${TestFile}) set_tests_properties(${_class}Test_${TestFile_Name} PROPERTIES ENVIRONMENT "TZ=${Ledger_TEST_TIMEZONE}") - set_target_properties(check - PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) endforeach() # CheckManpage and CheckTexinfo are disabled, since they do not work diff --git a/test/python/JournalTest.py b/test/python/JournalTest.py index e65c671d..2565ede8 100644 --- a/test/python/JournalTest.py +++ b/test/python/JournalTest.py @@ -22,6 +22,24 @@ class JournalTestCase(unittest.TestCase): for post in journal.query("food"): self.assertEqual(str(post.account), "Expenses:Food") self.assertEqual(post.amount, Amount("$21.34")) + + def testParseError(self): + # TODO: ledger spits out parse errors to standard out. + # This should not happen, especially when the error + # has already been captured by a Python exception. + def fun(): + read_journal_from_string(""" +2012-03-01 KFC + Expenses:Food rsnetnirsnti + Assets:Cash +""") + self.assertRaises(RuntimeError, fun) + try: + fun() + except RuntimeError as e: + self.assertEquals(str(e).splitlines()[-1], + "No quantity specified for amount") + def suite(): return unittest.TestLoader().loadTestsFromTestCase(JournalTestCase) diff --git a/test/regress/1187_5.test b/test/regress/1187_5.test new file mode 100644 index 00000000..4aa5fdd8 --- /dev/null +++ b/test/regress/1187_5.test @@ -0,0 +1,36 @@ +2013/12/01 * Initial State + Crédit:Viseca:MasterCard P1 -618.50 CHF + Crédit:Viseca:MasterCard P2 -52.10 CHF + Equity:Opening Balances + +2013/12/15 * Buy Some Chocolate + Dépenses:Nourriture 19.00 EUR ; #1 + Crédit:Viseca:MasterCard P1 + +2013/12/15 * Buy Some Chocolate + Crédit:Viseca:MasterCard P1 18.00 EUR ; #2 + Recettes:Erreurs + +2013/12/23 * Facture Viseca + Crédit:Viseca:MasterCard P2 52.10 CHF = 0 ; #3 + Crédit:Viseca:MasterCard P1 618.50 CHF = 0 CHF ; #4 + Dépenses:Frais:Gestion Comptes 1.50 CHF + Crédit:Viseca -672.10 CHF + +2014/01/03 * Facture Viseca + Crédit:Viseca 672.10 CHF = 0 + Actif:Comptes:CP courant + +test bal + -672.10 CHF Actif:Comptes:CP courant + -1.00 EUR Crédit:Viseca + -1.00 EUR MasterCard P1 + 1.50 CHF + 19.00 EUR Dépenses + 1.50 CHF Frais:Gestion Comptes + 19.00 EUR Nourriture + 670.60 CHF Equity:Opening Balances + -18.00 EUR Recettes:Erreurs +-------------------- + 0 +end test diff --git a/test/regress/1703.test b/test/regress/1703.test new file mode 100644 index 00000000..983b79be --- /dev/null +++ b/test/regress/1703.test @@ -0,0 +1,11 @@ + +P 2018-10-31 MultifundosPlus R$0 + +2017-05-03 * Test + Assets:A 1 AAA @ R$ 3000 + Assets:B + +test reg assets:a -V --now 2018-12-31 +17-May-03 Test Assets:A R$3000 R$3000 +end test + diff --git a/test/regress/1722.test b/test/regress/1722.test new file mode 100644 index 00000000..432a19b2 --- /dev/null +++ b/test/regress/1722.test @@ -0,0 +1,12 @@ + +2003/12/20 Organic Co-op + Expenses:Food:Groceries $ 37.50 ; ] [=2004/01/01] + Assets:Cash $-37.50 + +test bal + $ -37.50 Assets:Cash + $ 37.50 Expenses:Food:Groceries +-------------------- + 0 +end test + diff --git a/test/regress/1723.test b/test/regress/1723.test new file mode 100644 index 00000000..62a50386 --- /dev/null +++ b/test/regress/1723.test @@ -0,0 +1,5 @@ +2017/3/17 deferred posting + <deferred posting> + +test reg +end test diff --git a/test/garbage-input.dat b/test/regress/25A099C9.dat index 1b7d2101..1b7d2101 100644 --- a/test/garbage-input.dat +++ b/test/regress/25A099C9.dat diff --git a/test/regress/25A099C9.test b/test/regress/25A099C9.test index cfc0eabd..36776520 100644 --- a/test/regress/25A099C9.test +++ b/test/regress/25A099C9.test @@ -1,61 +1,61 @@ -test -f test/garbage-input.dat reg -> 29 +test -f test/regress/25A099C9.dat reg -> 29 __ERROR__ -While parsing file "$sourcepath/test/garbage-input.dat", line 1: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 1: Error: Directive '/*' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 32: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 32: Error: Directive '/**' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 36: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 36: Error: Directive '/**' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 66: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 66: Error: No quantity specified for amount -While parsing file "$sourcepath/test/garbage-input.dat", line 69: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 69: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 78: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 78: Error: Directive '};' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 82: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 82: Error: Directive '/**' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 93: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 93: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 97: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 97: Error: Directive '{' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 98: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 98: Error: Directive 'public:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 120: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 120: Error: Directive 'protected:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 131: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 131: Error: Directive 'public:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 711: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 711: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 740: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 740: Error: Directive 'private:' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 749: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 749: Error: Unexpected whitespace at beginning of line -While parsing file "$sourcepath/test/garbage-input.dat", line 750: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 750: Error: Directive '};' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 752: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 752: Error: Invalid date/time: line amount_t amoun -While parsing file "$sourcepath/test/garbage-input.dat", line 756: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 756: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 758: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 758: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/test/garbage-input.dat", line 762: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 762: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 764: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 764: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/test/garbage-input.dat", line 768: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 768: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 770: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 770: Error: Invalid date/time: line string amount_ -While parsing file "$sourcepath/test/garbage-input.dat", line 774: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 774: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 776: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 776: Error: Invalid date/time: line std::ostream& -While parsing file "$sourcepath/test/garbage-input.dat", line 782: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 782: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 783: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 783: Error: Invalid date/time: line std::istream& -While parsing file "$sourcepath/test/garbage-input.dat", line 786: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 786: Error: Directive '}' requires an argument -While parsing file "$sourcepath/test/garbage-input.dat", line 789: +While parsing file "$sourcepath/test/regress/25A099C9.dat", line 789: Error: Unexpected whitespace at beginning of line end test diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 9cacb4e7..3611b00a 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -20,7 +20,4 @@ if (BUILD_LIBRARY) target_link_libraries(MathTests ${PYTHON_LIBRARIES}) endif() add_ledger_test(MathTests) - - set_target_properties(check PROPERTIES DEPENDS LedgerUtilTests) - set_target_properties(check PROPERTIES DEPENDS LedgerMathTests) endif() diff --git a/tools/travis-before_install.sh b/tools/travis-before_install.sh deleted file mode 100755 index fe010945..00000000 --- a/tools/travis-before_install.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -#set -x -set -e -set -o pipefail - -if [ "${TRAVIS_OS_NAME}" = "osx" ]; then - brew update -fi - -if [ -n "${BOOST_VERSION}" ]; then - mkdir -p $BOOST_ROOT - wget --no-verbose --output-document=- \ - http://sourceforge.net/projects/boost/files/boost/${BOOST_VERSION}/boost_${BOOST_VERSION//./_}.tar.bz2/download \ - | tar jxf - --strip-components=1 -C "${BOOST_ROOT}" -fi diff --git a/tools/travis-install.sh b/tools/travis-install.sh deleted file mode 100755 index 4e8bdc48..00000000 --- a/tools/travis-install.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -#set -x -set -e -set -o pipefail - -if [ "${TRAVIS_OS_NAME}" = "osx" ]; then - for formula in $(echo "${BREWS//,/ }"); do - echo "Checking ${formula} formula" - brew outdated "${formula}" \ - || (brew unlink "${formula}" - brew install "${formula}" - ) - done -fi - -if [ -d "${BOOST_ROOT}" ]; then - (cd "${BOOST_ROOT}" - ./bootstrap.sh --with-libraries="${BOOST_LIBS}" - ./b2 threading=multi --prefix="${BOOST_ROOT}" -d0 install - ) -fi diff --git a/tools/update_copyright_year.sh b/tools/update_copyright_year.sh index db9541d4..ab08a947 100755 --- a/tools/update_copyright_year.sh +++ b/tools/update_copyright_year.sh @@ -5,7 +5,7 @@ # This script will replace the last year of Copyright statements with the first # argument of this script (defaulting to the current year). -# Copyright (c) 2016 Alexis Hildebrandt +# Copyright (c) 2016, 2019 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 @@ -31,6 +31,6 @@ YEAR=${1:-$(date +%Y)} GREP=${2:-egrep} ${GREP} -Rl 'Copyright.*Wiegley' $(git ls-files | cut -d / -f1 | uniq) \ - | ${GREP} -v "(test/garbage-input.dat|$(basename $0))" \ + | ${GREP} -v "(test/regress/25A099C9.dat|$(basename $0))" \ | xargs sed -i '' -e "s/\(Copyright.*\)-20[0-9]\{2\}/\1-${YEAR}/" |