summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el2
-rw-r--r--.gitmodules0
-rw-r--r--.travis.yml113
-rw-r--r--CMakeLists.txt15
-rw-r--r--CONTRIBUTING.md5
-rw-r--r--INSTALL.md12
-rw-r--r--LICENSE.md2
-rw-r--r--README.md41
-rwxr-xr-xacprep111
-rw-r--r--appveyor.yml2
-rw-r--r--cmake/FindUtfcpp.cmake3
-rw-r--r--contrib/ledger-completion.bash2
-rwxr-xr-xcontrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx2
-rw-r--r--default.nix5
-rw-r--r--doc/NEWS85
-rw-r--r--doc/ledger.15
-rw-r--r--doc/ledger3.texi191
-rw-r--r--src/CMakeLists.txt18
-rw-r--r--src/amount.cc4
-rw-r--r--src/annotate.cc4
-rw-r--r--src/balance.cc18
-rw-r--r--src/balance.h7
-rw-r--r--src/commodity.cc4
-rw-r--r--src/context.h31
-rw-r--r--src/draft.cc2
-rw-r--r--src/error.cc10
-rw-r--r--src/error.h17
-rw-r--r--src/expr.cc14
-rw-r--r--src/filters.cc25
-rw-r--r--src/filters.h1
-rw-r--r--src/format.cc4
-rw-r--r--src/global.h6
-rw-r--r--src/item.cc2
-rw-r--r--src/item.h14
-rw-r--r--src/main.cc19
-rw-r--r--src/mask.cc3
-rw-r--r--src/op.cc4
-rw-r--r--src/op.h22
-rw-r--r--src/option.cc5
-rw-r--r--src/output.cc7
-rw-r--r--src/print.cc4
-rw-r--r--src/py_post.cc5
-rw-r--r--src/py_value.cc8
-rw-r--r--src/pyinterp.cc16
-rw-r--r--src/query.cc1
-rw-r--r--src/quotes.cc7
-rw-r--r--src/report.cc9
-rw-r--r--src/report.h1
-rw-r--r--src/session.cc4
-rw-r--r--src/session.h4
-rw-r--r--src/stream.cc6
-rw-r--r--src/strptime.cc7
-rw-r--r--src/system.hh.in45
-rw-r--r--src/textual.cc88
-rw-r--r--src/timelog.cc2
-rw-r--r--src/times.cc118
-rw-r--r--src/token.cc4
-rw-r--r--src/utils.h10
-rw-r--r--src/value.cc6
-rw-r--r--src/value.h10
-rw-r--r--src/xact.cc14
-rw-r--r--src/xact.h6
-rw-r--r--test/CMakeLists.txt4
-rw-r--r--test/python/JournalTest.py18
-rw-r--r--test/regress/1127.test15
-rw-r--r--test/regress/1187_5.test36
-rw-r--r--test/regress/1222.test7
-rw-r--r--test/regress/1224.test9
-rw-r--r--test/regress/1626.test28
-rw-r--r--test/regress/1703.test11
-rw-r--r--test/regress/1722.test12
-rw-r--r--test/regress/1723.test5
-rw-r--r--test/regress/25A099C9.dat (renamed from test/garbage-input.dat)0
-rw-r--r--test/regress/25A099C9.test60
-rw-r--r--test/regress/7F3650FD.test2
-rw-r--r--test/regress/BBFA1759.test2
-rw-r--r--test/regress/fix-missing-trans-in-last-budget-period.test79
-rw-r--r--test/unit/CMakeLists.txt3
-rwxr-xr-xtools/travis-before_install.sh16
-rwxr-xr-xtools/travis-install.sh22
-rwxr-xr-xtools/update_copyright_year.sh4
81 files changed, 847 insertions, 698 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index de699515..34761815 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -4,7 +4,7 @@
((nil
(tab-width . 2)
(sentence-end-double-space . t)
- (bug-reference-url-format . "http://bugs.ledger-cli.org/show_bug.cgi?id=%s"))
+ (bug-reference-url-format . "https://github.com/ledger/ledger/issues/%s"))
(c-mode
(c-file-style . "ledger")
(c-style-alist
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29b..00000000
--- a/.gitmodules
+++ /dev/null
diff --git a/.travis.yml b/.travis.yml
index 4def903d..825ad6d7 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,7 +5,6 @@
language: cpp
compiler:
- gcc
- - clang
os:
- linux
- osx
@@ -18,41 +14,24 @@ cache:
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:
+ # Boost version to build against; an empty string means the
+ # distribution's default.
+ - BOOST_VERSION=""
+ - BOOST_VERSION="1.61.0"
+# The configuration for macOS only works with Boost installed by
+# homebrew, so exclude the other combinations.
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
+ env: BOOST_VERSION=""
- 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
+ env: BOOST_VERSION="1.61.0"
addons:
coverity_scan:
@@ -63,51 +42,58 @@ 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
+ homebrew:
+ packages:
+ - boost
+ - boost-python
+ - gmp
+ - mpfr
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
- - make
+ # On macOS, CMake finds the Boost.Python installed by homebrew only
+ # with the component name "python27". Also, precompiling system.hh
+ # does not work.
+ - if [ "$TRAVIS_OS_NAME" = osx -a -z "$BOOST_VERSION" ]; then EXTRA_CMAKE_ARGS="-DPRECOMPILE_SYSTEM_HH=OFF -DUSE_PYTHON27_COMPONENT=ON"; fi
+ - cmake . -DUSE_PYTHON=ON -DBUILD_DEBUG=ON $EXTRA_CMAKE_ARGS
+ - make VERBOSE=1
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
@@ -116,3 +102,10 @@ notifications:
channels: [ "chat.freenode.net#ledger" ]
on_success: change
on_failure: change
+ webhooks:
+ urls:
+ - https://webhooks.gitter.im/e/0050d91909a8cde39e35
+ on_success: change # options: [always|never|change] default: always
+ on_failure: always # options: [always|never|change] default: always
+ on_start: never # options: [always|never|change] default: always
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c49ddee6..7ee95b0b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,10 +27,12 @@ endif()
########################################################################
option(USE_PYTHON "Build support for the Python scripting bridge" OFF)
+OPTION(USE_PYTHON27_COMPONENT "Use python27 as name of Boost.Python component" 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(PRECOMPILE_SYSTEM_HH "Precompile system.hh" ON)
option(BUILD_LIBRARY "Build and install Ledger as a library" ON)
option(BUILD_DOCS "Build and install documentation" OFF)
@@ -67,7 +69,11 @@ if (USE_PYTHON)
find_package(PythonLibs)
if (PYTHONLIBS_FOUND)
- set(BOOST_PYTHON python)
+ if(USE_PYTHON27_COMPONENT)
+ set(BOOST_PYTHON "python27")
+ else()
+ set(BOOST_PYTHON "python")
+ endif()
set(HAVE_BOOST_PYTHON 1)
include_directories(SYSTEM ${PYTHON_INCLUDE_DIRS})
else()
@@ -96,8 +102,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 +264,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")
@@ -298,8 +302,7 @@ configure_file(
${PROJECT_SOURCE_DIR}/src/system.hh.in
${PROJECT_BINARY_DIR}/system.hh)
-if((CMAKE_CXX_COMPILER MATCHES "clang") OR
- (CMAKE_CXX_COMPILER MATCHES "clang\\+\\+"))
+if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem ")
endif()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 79e97296..80a413f3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ Tips for contributors
branch and is subsequently merged into `master` for releases.
* If you're making **changes to files for which the Travis build is not
relevant**, please **add `[ci skip]` to the end of the commit message**.
-* Report bugs using [Bugzilla]. If you want, you can access the bug system using the lite (free) version of [Deskzilla].
+* Report bugs using [GitHub Issues].
GLOSSARY
----
@@ -119,8 +119,7 @@ cores:
[Boost]: http://boost.org
[Boost.Python]: http://www.boost.org/libs/python/
-[Bugzilla]: http://bugs.ledger-cli.org/
-[Deskzilla]: http://almworks.com/deskzilla/download.html
+[GitHub Issues]: https://github.com/ledger/ledger/issues
[GMP]: http://gmplib.org/
[MPFR]: http://www.mpfr.org/
[Cheetah]: http://www.cheetahtemplate.org
diff --git a/INSTALL.md b/INSTALL.md
index 62de2a97..1d5bedba 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -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
----------------------------------------------------------------------
@@ -96,10 +96,10 @@ A: Actually, the real segfault is in libstdc++'s facet code. It's being
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:
+A: This, I am most interested in hearing about. Please file a bug at the
+ Ledger Issue Tracker, https://github.com/ledger/ledger/issues. The more
+ details you can provide, the better. Also, if Ledger is crashing, try
+ running it under gdb like so:
$ gdb ledger
(gdb) run <ARGS TO LEDGER>
@@ -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.
diff --git a/LICENSE.md b/LICENSE.md
index 93cc207c..36dc7cfd 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,4 +1,4 @@
-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
diff --git a/README.md b/README.md
index cb7ba18c..de083f7e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
+[![Join the chat at https://gitter.im/use-package/Lobby](https://badges.gitter.im/use-package/Lobby.svg)](https://gitter.im/use-package/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![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
@@ -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:
-
-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
+### macOS
-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 testing (stretch) 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
diff --git a/acprep b/acprep
index d1c24388..c5b4c203 100755
--- a/acprep
+++ b/acprep
@@ -12,11 +12,7 @@ import optparse
import os
import re
import shutil
-import string
import sys
-import time
-import tempfile
-import datetime
try:
import hashlib
@@ -57,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',
@@ -74,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',
@@ -96,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',
@@ -514,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',
@@ -535,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',
@@ -544,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)
@@ -552,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',
@@ -574,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',
@@ -594,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',
@@ -613,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',
@@ -632,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)
@@ -696,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 #
@@ -1090,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/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx b/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx
index 937d2a45..f2055a62 100755
--- a/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx
+++ b/contrib/non-profit-audit-reports/cash-receipts-and-disbursments-journals.plx
@@ -127,7 +127,7 @@ foreach my $typeData ({ name => 'disbursements', query => 'a<=0' },
# 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
+ # I opened a bug: https://github.com/ledger/ledger/issues/901
my @csvRegLedgerOpts = ('-f', $tempFile, '-V', '-F', $formatString, '-w', '--sort', 'd',
'-b', $beginDate, '-e', $endDate, 'reg');
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";
diff --git a/doc/NEWS b/doc/NEWS
index 0cb4b2b9..a59a1b1a 100644
--- a/doc/NEWS
+++ b/doc/NEWS
@@ -1,33 +1,80 @@
Ledger NEWS
-* 3.1.2
+* 3.1.2 (2019-02-05)
-- 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
+
+- Fix 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)
+
+- Fix possible stack overflow in option parsing routine (bug #1222,
+ CVE-2017-12481)
+
+- Fix possible stack overflow in date parsing routine (bug #1224,
+ CVE-2017-12482)
+
+- Fix use-after-free when using --gain (bug #541)
- 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 +104,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 +201,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 +211,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 +230,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 +242,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 +260,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/ledger.1 b/doc/ledger.1
index 85a9937b..a76750ba 100644
--- a/doc/ledger.1
+++ b/doc/ledger.1
@@ -214,6 +214,9 @@ 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'
+The sort order may be controlled with the '-' sign. For example, to sort in
+reverse chronological order:
+.Dl ledger reg --sort '-date'
.It Fl \-tail Ar number
Only show the last
.Ar number
@@ -578,7 +581,7 @@ Direct
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
+.It Fl \-file Ar FILE Pq Fl f
Read journal data from
.Ar FILE .
.It Fl \-first Ar INT
diff --git a/doc/ledger3.texi b/doc/ledger3.texi
index a955be9a..0ac62b29 100644
--- a/doc/ledger3.texi
+++ b/doc/ledger3.texi
@@ -111,7 +111,7 @@
@copying
-Copyright @copyright{} 2003--2018, John Wiegley. All rights reserved.
+Copyright @copyright{} 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
@@ -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
@@ -2255,6 +2256,41 @@ 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 apply 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
+apply 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
+end apply fixed
+@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
+
@item alias
@findex alias
@cindex account, alias
@@ -2402,6 +2438,7 @@ commodity $
note American Dollars
format $1,000.00
nomarket
+ alias USD
default
@end smallexample
@@ -2416,6 +2453,9 @@ provide the ``canonical'' representation.
The @code{nomarket} sub-directive states that the commodity's price
should never be auto-downloaded.
+The @code{alias} sub-directive states that any commodity matching this
+symbol is to use the commodity declared in this block.
+
The @code{default} sub-directive marks this as the ``default'' commodity.
@item define
@@ -2436,59 +2476,18 @@ 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}.
+Closes block commands like @code{apply} 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.
+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
@@ -2914,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
@@ -3105,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
@@ -3189,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
@@ -3232,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
@@ -3508,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
@@ -3527,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
@@ -3608,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
@@ -3625,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
@@ -8091,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
@@ -8161,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
@@ -9218,7 +9281,7 @@ looking at its @code{xact} member:
last_xact = None
for post in ledger.read_journal("sample.dat").query(""):
if post.xact != last_xact:
- for post in post.xact.posts:
+ for post in post.xact.posts():
print "Transferring %s to/from %s" % (post.amount,
post.account)
last_xact = post.xact
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 71d9478a..9b39ea94 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,11 +133,18 @@ 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\\+\\+")
+ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
add_definitions(
# -Weverything
# -Wno-disabled-macro-expansion
@@ -193,7 +199,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
DEPENDS ${_header_filename})
endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs)
- elseif (CMAKE_CXX_COMPILER MATCHES "g\\+\\+")
+ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GXX_WARNING_FLAGS
-pedantic
-Wall
@@ -261,7 +267,9 @@ else()
endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs)
endif()
-add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES LEDGER_CLI_SOURCES)
+if(PRECOMPILE_SYSTEM_HH)
+ add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES LEDGER_CLI_SOURCES)
+endif()
include(GNUInstallDirs)
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/balance.cc b/src/balance.cc
index fa1bc20c..1ae9edb2 100644
--- a/src/balance.cc
+++ b/src/balance.cc
@@ -240,6 +240,14 @@ balance_t::strip_annotations(const keep_details_t& what_to_keep) const
return temp;
}
+void balance_t::sorted_amounts(amounts_array& sorted) const
+{
+ foreach (const amounts_map::value_type& pair, amounts)
+ sorted.push_back(&pair.second);
+ std::stable_sort(sorted.begin(), sorted.end(),
+ commodity_t::compare_by_commodity());
+}
+
void balance_t::map_sorted_amounts(function<void(const amount_t&)> fn) const
{
if (! amounts.empty()) {
@@ -249,16 +257,8 @@ void balance_t::map_sorted_amounts(function<void(const amount_t&)> fn) const
fn(amount);
}
else {
- typedef std::vector<const amount_t *> amounts_array;
amounts_array sorted;
-
- foreach (const amounts_map::value_type& pair, amounts)
- if (pair.second)
- sorted.push_back(&pair.second);
-
- std::stable_sort(sorted.begin(), sorted.end(),
- commodity_t::compare_by_commodity());
-
+ sorted_amounts(sorted);
foreach (const amount_t * amount, sorted)
fn(*amount);
}
diff --git a/src/balance.h b/src/balance.h
index 8e773fc5..b9c9c2c8 100644
--- a/src/balance.h
+++ b/src/balance.h
@@ -81,6 +81,7 @@ class balance_t
{
public:
typedef std::map<commodity_t *, amount_t> amounts_map;
+ typedef std::vector<const amount_t *> amounts_array;
amounts_map amounts;
@@ -529,6 +530,12 @@ public:
balance_t strip_annotations(const keep_details_t& what_to_keep) const;
/**
+ * Given a balance, insert a commodity-wise sort of the amounts into the
+ * given amounts_array.
+ */
+ void sorted_amounts(amounts_array& sorted) 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
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/filters.cc b/src/filters.cc
index 4e9e633a..3dfd2327 100644
--- a/src/filters.cc
+++ b/src/filters.cc
@@ -1245,19 +1245,34 @@ void generate_posts::add_post(const date_interval_t& period, post_t& post)
void budget_posts::report_budget_items(const date_t& date)
{
+ { // Cleanup pending items that finished before date
+ // We have to keep them until the last day they apply because operator() needs them to see if a
+ // posting is budgeted or not
+ std::list<pending_posts_list::iterator> posts_to_erase;
+ for (pending_posts_list::iterator i = pending_posts.begin(); i != pending_posts.end(); i++) {
+ pending_posts_list::value_type& pair(*i);
+ if (pair.first.finish && ! pair.first.start && pair.first.finish < date) {
+ posts_to_erase.push_back(i);
+ }
+ }
+ foreach (pending_posts_list::iterator& i, posts_to_erase)
+ pending_posts.erase(i);
+ }
+
if (pending_posts.size() == 0)
return;
bool reported;
do {
- std::list<pending_posts_list::iterator> 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);
+ if (pair.first.finish && ! pair.first.start)
+ continue; // skip expired posts
+
optional<date_t> begin = pair.first.start;
if (! begin) {
optional<date_t> range_begin;
@@ -1285,9 +1300,6 @@ void budget_posts::report_budget_items(const date_t& date)
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());
@@ -1312,9 +1324,6 @@ void budget_posts::report_budget_items(const date_t& date)
reported = true;
}
}
-
- foreach (pending_posts_list::iterator& i, posts_to_erase)
- pending_posts.erase(i);
} while (reported);
}
diff --git a/src/filters.h b/src/filters.h
index c1dc2e04..cf053a24 100644
--- a/src/filters.h
+++ b/src/filters.h
@@ -604,6 +604,7 @@ public:
virtual ~changed_value_posts() {
TRACE_DTOR(changed_value_posts);
+ temps.clear();
handler.reset();
}
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..4eb02571 100644
--- a/src/global.h
+++ b/src/global.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
@@ -129,7 +129,7 @@ public:
out << '-' << Ledger_VERSION_DATE;
out << _(", the command-line accounting tool");
out <<
- _("\n\nCopyright (c) 2003-2018, John Wiegley. All rights reserved.\n\n\
+ _("\n\nCopyright (c) 2003-2019, John Wiegley. All rights reserved.\n\n\
This program is made available under the terms of the BSD Public License.\n\
See LICENSE file included with the distribution for details and disclaimer.");
out << std::endl;
@@ -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';
diff --git a/src/item.h b/src/item.h
index a28bf59e..51539eff 100644
--- a/src/item.h
+++ b/src/item.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
@@ -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;
diff --git a/src/op.cc b/src/op.cc
index 00b84459..5801b1dd 100644
--- a/src/op.cc
+++ b/src/op.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
@@ -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 << " ";
diff --git a/src/op.h b/src/op.h
index bbbb5d81..aab362c7 100644
--- a/src/op.h
+++ b/src/op.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
@@ -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/option.cc b/src/option.cc
index ab6c37e0..81f9af5b 100644
--- a/src/option.cc
+++ b/src/option.cc
@@ -42,6 +42,11 @@ namespace {
{
char buf[128];
char * p = buf;
+
+ if (name.length() > 127) {
+ throw_(option_error, _f("Illegal option --%1%") % name);
+ }
+
foreach (char ch, name) {
if (ch == '-')
*p++ = '_';
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/print.cc b/src/print.cc
index 9fa75eab..92323777 100644
--- a/src/print.cc
+++ b/src/print.cc
@@ -103,11 +103,13 @@ namespace {
void print_xact(report_t& report, std::ostream& out, xact_t& xact)
{
format_type_t format_type = FMT_WRITTEN;
+ string format_str;
optional<const char *> format;
if (report.HANDLED(date_format_)) {
format_type = FMT_CUSTOM;
- format = report.HANDLER(date_format_).str().c_str();
+ format_str = report.HANDLER(date_format_).str();
+ format = format_str.c_str();
}
std::ostringstream buf;
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/report.cc b/src/report.cc
index cb7f09dc..13e6a61f 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -841,6 +841,13 @@ value_t report_t::fn_commodity(call_scope_t& args)
return string_value(args.get<amount_t>(0).commodity().symbol());
}
+value_t report_t::fn_clear_commodity(call_scope_t& args)
+{
+ amount_t amt(args.get<amount_t>(0));
+ amt.clear_commodity();
+ return amt;
+}
+
value_t report_t::fn_nail_down(call_scope_t& args)
{
value_t arg0(args[0]);
@@ -1384,6 +1391,8 @@ expr_t::ptr_op_t report_t::lookup(const symbol_t::kind_t kind,
return MAKE_FUNCTOR(report_t::fn_commodity);
else if (is_eq(p, "ceiling"))
return MAKE_FUNCTOR(report_t::fn_ceiling);
+ else if (is_eq(p, "clear_commodity"))
+ return MAKE_FUNCTOR(report_t::fn_clear_commodity);
break;
case 'd':
diff --git a/src/report.h b/src/report.h
index 635c3887..1bda0407 100644
--- a/src/report.h
+++ b/src/report.h
@@ -175,6 +175,7 @@ public:
value_t fn_truncated(call_scope_t& scope);
value_t fn_floor(call_scope_t& scope);
value_t fn_ceiling(call_scope_t& scope);
+ value_t fn_clear_commodity(call_scope_t& scope);
value_t fn_round(call_scope_t& scope);
value_t fn_roundto(call_scope_t& scope);
value_t fn_unround(call_scope_t& scope);
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/timelog.cc b/src/timelog.cc
index 2a618afd..5b289a34 100644
--- a/src/timelog.cc
+++ b/src/timelog.cc
@@ -170,7 +170,7 @@ void time_log_t::close()
foreach (account_t * account, accounts) {
DEBUG("timelog", "Clocking out from account " << account->fullname());
context.count += clock_out_from_timelog
- (time_xacts, time_xact_t(none, CURRENT_TIME(), account), context);
+ (time_xacts, time_xact_t(none, CURRENT_TIME(), false, account), context);
}
assert(time_xacts.empty());
}
diff --git a/src/times.cc b/src/times.cc
index 8e4df020..74773755 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,
@@ -206,7 +127,9 @@ namespace {
date_t parse_date_mask_routine(const char * date_str, date_io_t& io,
date_traits_t * traits = NULL)
{
- VERIFY(std::strlen(date_str) < 127);
+ if (std::strlen(date_str) > 127) {
+ throw_(date_error, _f("Invalid date: %1%") % date_str);
+ }
char buf[128];
std::strcpy(buf, date_str);
@@ -420,7 +343,6 @@ class date_parser_t
TOK_DASH,
TOK_DOT,
- TOK_A_YEAR,
TOK_A_MONTH,
TOK_A_WDAY,
@@ -512,9 +434,6 @@ class date_parser_t
case TOK_SLASH: return "/";
case TOK_DASH: return "-";
case TOK_DOT: return ".";
- case TOK_A_YEAR:
- out << boost::get<date_specifier_t::year_type>(*value);
- break;
case TOK_A_MONTH:
out << date_specifier_t::month_type
(boost::get<date_time::months_of_year>(*value));
@@ -566,7 +485,6 @@ class date_parser_t
case TOK_SLASH: out << "TOK_SLASH"; break;
case TOK_DASH: out << "TOK_DASH"; break;
case TOK_DOT: out << "TOK_DOT"; break;
- case TOK_A_YEAR: out << "TOK_A_YEAR"; break;
case TOK_A_MONTH: out << "TOK_A_MONTH"; break;
case TOK_A_WDAY: out << "TOK_A_WDAY"; break;
case TOK_AGO: out << "TOK_AGO"; break;
@@ -727,7 +645,11 @@ void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
when += gregorian::days(amount * adjust);
break;
default:
- specifier.day = date_specifier_t::day_type(amount);
+ if (amount > 31) {
+ specifier.year = date_specifier_t::year_type(amount);
+ } else {
+ specifier.day = date_specifier_t::day_type(amount);
+ }
break;
}
@@ -832,16 +754,13 @@ void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok,
break;
}
- case lexer_t::token_t::TOK_A_YEAR:
- specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
- break;
case lexer_t::token_t::TOK_A_MONTH:
specifier.month =
date_specifier_t::month_type
(boost::get<date_time::months_of_year>(*tok.value));
tok = lexer.peek_token();
switch (tok.kind) {
- case lexer_t::token_t::TOK_A_YEAR:
+ case lexer_t::token_t::TOK_INT:
specifier.year = boost::get<date_specifier_t::year_type>(*tok.value);
break;
case lexer_t::token_t::END_REACHED:
@@ -898,12 +817,6 @@ date_interval_t date_parser_t::parse()
determine_when(tok, *inclusion_specifier);
break;
- case lexer_t::token_t::TOK_A_YEAR:
- if (! inclusion_specifier)
- inclusion_specifier = date_specifier_t();
- determine_when(tok, *inclusion_specifier);
- break;
-
case lexer_t::token_t::TOK_A_MONTH:
if (! inclusion_specifier)
inclusion_specifier = date_specifier_t();
@@ -1612,13 +1525,8 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token()
if (! term.empty()) {
if (std::isdigit(term[0])) {
- if (term.length() == 4)
- return token_t(token_t::TOK_A_YEAR,
- token_t::content_t
- (lexical_cast<date_specifier_t::year_type>(term)));
- else
- return token_t(token_t::TOK_INT,
- token_t::content_t(lexical_cast<unsigned short>(term)));
+ return token_t(token_t::TOK_INT,
+ token_t::content_t(lexical_cast<unsigned short>(term)));
}
else if (std::isalpha(term[0])) {
to_lower(term);
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 d29072d4..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
- post->account->add_post(post);
+ 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);
@@ -806,6 +808,10 @@ void auto_xact_t::extend_xact(xact_base_t& xact, parse_context_t& context)
xact.add_post(new_post);
new_post->account->add_post(new_post);
+ // Add flags so this post updates the account balance
+ new_post->xdata().add_flags(POST_EXT_VISITED);
+ new_post->account->xdata().add_flags(ACCOUNT_EXT_VISITED);
+
if (new_post->must_balance())
needs_further_verification = true;
}
diff --git a/src/xact.h b/src/xact.h
index b0a4417e..26e263fc 100644
--- a/src/xact.h
+++ b/src/xact.h
@@ -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/1127.test b/test/regress/1127.test
new file mode 100644
index 00000000..905401f8
--- /dev/null
+++ b/test/regress/1127.test
@@ -0,0 +1,15 @@
+; Test that automated transactions are added to accounts soon enough
+; for assertions to work.
+
+= expr account =~ /^Assets/
+ (Foo) 1
+
+2018-06-09 Something
+ Assets $100
+ Equity
+
+2018-06-09 Assert amount added by automated transaction
+ [Foo] = $100
+test bal Foo
+ $100 Foo
+end test
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/1222.test b/test/regress/1222.test
new file mode 100644
index 00000000..535a0e32
--- /dev/null
+++ b/test/regress/1222.test
@@ -0,0 +1,7 @@
+--fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
+
+test reg -> 1
+__ERROR__
+While parsing file "$FILE", line 1:
+Error: Illegal option --fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
+end test
diff --git a/test/regress/1224.test b/test/regress/1224.test
new file mode 100644
index 00000000..ecf87228
--- /dev/null
+++ b/test/regress/1224.test
@@ -0,0 +1,9 @@
+2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+
+test reg -> 1
+__ERROR__
+While parsing file "$FILE", line 1:
+While parsing transaction:
+> 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+Error: Invalid date: 2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+end test
diff --git a/test/regress/1626.test b/test/regress/1626.test
new file mode 100644
index 00000000..89ae80f8
--- /dev/null
+++ b/test/regress/1626.test
@@ -0,0 +1,28 @@
+test period every 1000 years from 1 Sep 2011 to 30 May 2012 --now=2018-06-10
+--- Period expression tokens ---
+TOK_EVERY: every
+TOK_INT: 1000
+TOK_YEARS: years
+TOK_SINCE: since
+TOK_INT: 1
+TOK_A_MONTH: Sep
+TOK_INT: 2011
+TOK_UNTIL: until
+TOK_INT: 30
+TOK_A_MONTH: May
+TOK_INT: 2012
+END_REACHED: <EOF>
+
+--- Before stabilization ---
+ range: from day 1 to day 30
+duration: 1000 years
+
+--- After stabilization ---
+ range: from day 1 to day 30
+ start: 18-Jan-01
+ finish: 18-Jan-30
+duration: 1000 years
+
+--- Sample dates in range (max. 20) ---
+ 1: 18-Jan-01 -- 18-Jan-29
+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/regress/7F3650FD.test b/test/regress/7F3650FD.test
index f0498ddb..0ccfe644 100644
--- a/test/regress/7F3650FD.test
+++ b/test/regress/7F3650FD.test
@@ -68,7 +68,7 @@ end test
test period --now=2010/11/01 2009
--- Period expression tokens ---
-TOK_A_YEAR: 2009
+TOK_INT: 2009
END_REACHED: <EOF>
--- Before stabilization ---
diff --git a/test/regress/BBFA1759.test b/test/regress/BBFA1759.test
index 7a402d0c..5df7ecb2 100644
--- a/test/regress/BBFA1759.test
+++ b/test/regress/BBFA1759.test
@@ -2,7 +2,7 @@
test period june 2008
--- Period expression tokens ---
TOK_A_MONTH: Jun
-TOK_A_YEAR: 2008
+TOK_INT: 2008
END_REACHED: <EOF>
--- Before stabilization ---
diff --git a/test/regress/fix-missing-trans-in-last-budget-period.test b/test/regress/fix-missing-trans-in-last-budget-period.test
new file mode 100644
index 00000000..163a0540
--- /dev/null
+++ b/test/regress/fix-missing-trans-in-last-budget-period.test
@@ -0,0 +1,79 @@
+= ~ ^A
+ [Balance] 1
+ [Budget:$account] -1
+
+~ Monthly from 2014/01 to 2014/12/31
+ [Budget:A] 100.00 USD
+ [Balance]
+
+~ Monthly from 2014/01 to 2014/12/31
+ [Budget:Z] 100.00 USD
+ [Balance]
+
+2014/10/01 toto0
+ [Budget:A:B] 0.01 USD
+ [Balance]
+
+2014/11/01 toto1
+ A:B 51.00 USD
+ Cash
+
+2014/11/02 toto2
+ A:B 52.00 USD
+ Cash
+
+2014/11/03 toto3
+ A:B 53.00 USD
+ Cash
+
+2014/11/04 toto4
+ A:B 54.00 USD
+ Cash
+
+2014/12/08 toto5
+ A:B 55.00 USD
+ Cash
+
+2014/12/09 toto6
+ A:B 56.00 USD
+ Cash
+
+2014/12/10 toto7
+ A:B 57.00 USD
+ Cash
+
+2014/12/11 toto8
+ A:B 58.00 USD
+ Cash
+
+2014/12/12 toto9
+ A:B 59.00 USD
+ Cash
+
+2014/12/12 toto9
+ C 59.00 USD
+ Cash
+
+2015/01/12 toto10
+ A:B 59.00 USD
+ Cash
+
+test reg --budget -b 2014/10 -e 2015/02 --columns 80 --date-format "%F" reg ^Bu
+2014-10-01 Budget transaction [Budget:A] -100.00 USD -100.00 USD
+2014-10-01 Budget transaction [Budget:Z] -100.00 USD -200.00 USD
+2014-10-01 toto0 [Budget:A] 0.01 USD -199.99 USD
+2014-11-01 Budget transaction [Budget:A] -100.00 USD -299.99 USD
+2014-11-01 Budget transaction [Budget:Z] -100.00 USD -399.99 USD
+2014-11-01 toto1 [Budget:A] -51.00 USD -450.99 USD
+2014-11-02 toto2 [Budget:A] -52.00 USD -502.99 USD
+2014-11-03 toto3 [Budget:A] -53.00 USD -555.99 USD
+2014-11-04 toto4 [Budget:A] -54.00 USD -609.99 USD
+2014-12-01 Budget transaction [Budget:A] -100.00 USD -709.99 USD
+2014-12-01 Budget transaction [Budget:Z] -100.00 USD -809.99 USD
+2014-12-08 toto5 [Budget:A] -55.00 USD -864.99 USD
+2014-12-09 toto6 [Budget:A] -56.00 USD -920.99 USD
+2014-12-10 toto7 [Budget:A] -57.00 USD -977.99 USD
+2014-12-11 toto8 [Budget:A] -58.00 USD -1035.99 USD
+2014-12-12 toto9 [Budget:A] -59.00 USD -1094.99 USD
+2015-01-12 toto10 [Budget:A] -59.00 USD -1153.99 USD
+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}/"