diff options
-rw-r--r-- | .travis.yml | 10 | ||||
-rw-r--r-- | CMakeLists.txt | 39 | ||||
-rw-r--r-- | default.nix | 39 | ||||
-rw-r--r-- | doc/LICENSE-sha1 | 13 | ||||
-rw-r--r-- | lib/sha1.cpp | 589 | ||||
-rw-r--r-- | lib/sha1.h | 89 | ||||
-rw-r--r-- | src/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/account.cc | 30 | ||||
-rw-r--r-- | src/account.h | 18 | ||||
-rw-r--r-- | src/filters.cc | 14 | ||||
-rw-r--r-- | src/journal.cc | 67 | ||||
-rw-r--r-- | src/journal.h | 1 | ||||
-rw-r--r-- | src/post.h | 1 | ||||
-rw-r--r-- | src/session.cc | 16 | ||||
-rw-r--r-- | src/system.hh.in | 3 | ||||
-rw-r--r-- | src/textual.cc | 35 | ||||
-rw-r--r-- | src/utils.h | 15 | ||||
-rw-r--r-- | src/xact.cc | 5 | ||||
-rw-r--r-- | test/CMakeLists.txt | 23 | ||||
-rw-r--r-- | test/baseline/feat-balance_assert_split.test | 60 | ||||
-rwxr-xr-x | tools/build.sh | 4 |
21 files changed, 286 insertions, 791 deletions
diff --git a/.travis.yml b/.travis.yml index eed18531..eefc1859 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,14 @@ language: cpp compiler: - clang - gcc -install: "./acprep dependencies" -before_script: "./acprep opt make --python" +install: + - "./acprep dependencies" + - wget http://sourceforge.net/projects/boost/files/boost/1.55.0/boost_1_55_0.tar.bz2/download + - tar jxf download + - mv boost_1_55_0 $HOME/boost-trunk +before_script: + - BOOST="$HOME/boost-trunk" + - "./acprep opt make --python" script: - "./acprep check -- --output-on-failure" - "PYTHONPATH=. python python/demo.py" diff --git a/CMakeLists.txt b/CMakeLists.txt index c60cdce2..b7289748 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,13 @@ PROJECT(ledger) set(Ledger_VERSION_MAJOR 3) set(Ledger_VERSION_MINOR 0) -set(Ledger_VERSION_PATCH 1) -set(Ledger_VERSION_DATE 20140327) +set(Ledger_VERSION_PATCH 2) +set(Ledger_VERSION_DATE 20140417) enable_testing() +add_definitions(-std=c++11) + ######################################################################## option(USE_PYTHON "Build support for the Python scripting bridge" OFF) @@ -65,7 +67,7 @@ else() endif() # Set BOOST_ROOT to help CMake to find the right Boost version -find_package(Boost 1.46.0 +find_package(Boost 1.55.0 REQUIRED date_time filesystem system iostreams regex unit_test_framework ${BOOST_PYTHON}) @@ -148,37 +150,6 @@ endif() cmake_pop_check_state() -#cmake_push_check_state() -# -#set(CMAKE_REQUIRED_FLAGS -std=c++11) -#set(CMAKE_REQUIRED_INCLUDES ${CMAKE_INCLUDE_PATH}) -# -#check_cxx_source_runs(" -##include <regex> -##include <vector> -##include <iostream> -# -#int main() { -# std::vector<int> x {0, 1, 2, 3, 4}; -# for (auto i : x) -# std::cout << i << std::endl; -# -# std::regex r(\"foo\"); -# std::cout << std::regex_match(\"foobar\", r) << std::endl; -# return 0; -#}" CXX11_RUNS) -# -#cmake_pop_check_state() -# -#if(CXX11_RUNS) -# set(HAVE_CXX11 1) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -#else() - set(HAVE_CXX11 0) -#endif() -# -#cmake_pop_check_state() - ######################################################################## include_directories(${CMAKE_INCLUDE_PATH}) diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..f1745ce4 --- /dev/null +++ b/default.nix @@ -0,0 +1,39 @@ +{ stdenv, fetchgit, cmake, boost, gmp, mpfr, libedit, python +, texinfo, gnused }: + +let + rev = "20140417"; +in + +stdenv.mkDerivation { + name = "ledger-3.0.2.${rev}"; + + src = ./.; + + buildInputs = [ cmake boost gmp mpfr libedit python texinfo gnused ]; + + enableParallelBuilding = true; + + # Skip byte-compiling of emacs-lisp files because this is currently + # broken in ledger... + postInstall = '' + mkdir -p $out/share/emacs/site-lisp/ + cp -v $src/lisp/*.el $out/share/emacs/site-lisp/ + ''; + + meta = { + homepage = "http://ledger-cli.org/"; + description = "A double-entry accounting system with a command-line reporting interface"; + license = "BSD"; + + longDescription = '' + Ledger is a powerful, double-entry accounting system that is accessed + from the UNIX command-line. This may put off some users, as there is + no flashy UI, but for those who want unparalleled reporting access to + their data, there really is no alternative. + ''; + + platforms = stdenv.lib.platforms.all; + maintainers = with stdenv.lib.maintainers; [ simons the-kenny jwiegley ]; + }; +} diff --git a/doc/LICENSE-sha1 b/doc/LICENSE-sha1 deleted file mode 100644 index 8d886177..00000000 --- a/doc/LICENSE-sha1 +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (C) 1998
-Paul E. Jones <paulej@arid.us>
-All Rights Reserved.
-
-This software is licensed as "freeware." Permission to distribute
-this software in source and binary forms is hereby granted without
-a fee. THIS SOFTWARE IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED
-OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-THE AUTHOR SHALL NOT BE HELD LIABLE FOR ANY DAMAGES RESULTING
-FROM THE USE OF THIS SOFTWARE, EITHER DIRECTLY OR INDIRECTLY, INCLUDING,
-BUT NOT LIMITED TO, LOSS OF DATA OR DATA BEING RENDERED INACCURATE.
-
diff --git a/lib/sha1.cpp b/lib/sha1.cpp deleted file mode 100644 index 1048b157..00000000 --- a/lib/sha1.cpp +++ /dev/null @@ -1,589 +0,0 @@ -/* - * sha1.cpp - * - * Copyright (C) 1998 - * Paul E. Jones <paulej@arid.us> - * All Rights Reserved. - * - ***************************************************************************** - * $Id: sha1.cpp,v 1.9 2004/03/27 18:02:20 paulej Exp $ - ***************************************************************************** - * - * Description: - * This class implements the Secure Hashing Standard as defined - * in FIPS PUB 180-1 published April 17, 1995. - * - * The Secure Hashing Standard, which uses the Secure Hashing - * Algorithm (SHA), produces a 160-bit message digest for a - * given data stream. In theory, it is highly improbable that - * two messages will produce the same message digest. Therefore, - * this algorithm can serve as a means of providing a "fingerprint" - * for a message. - * - * Portability Issues: - * SHA-1 is defined in terms of 32-bit "words". This code was - * written with the expectation that the processor has at least - * a 32-bit machine word size. If the machine word size is larger, - * the code should still function properly. One caveat to that - * is that the input functions taking characters and character arrays - * assume that only 8 bits of information are stored in each character. - * - * Caveats: - * SHA-1 is designed to work with messages less than 2^64 bits long. - * Although SHA-1 allows a message digest to be generated for - * messages of any number of bits less than 2^64, this implementation - * only works with messages with a length that is a multiple of 8 - * bits. - * - */ - - -#include "sha1.h" - -/* - * SHA1 - * - * Description: - * This is the constructor for the sha1 class. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -SHA1::SHA1() -{ - Reset(); -} - -/* - * ~SHA1 - * - * Description: - * This is the destructor for the sha1 class - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -SHA1::~SHA1() -{ - // The destructor does nothing -} - -/* - * Reset - * - * Description: - * This function will initialize the sha1 class member variables - * in preparation for computing a new message digest. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Reset() -{ - Length_Low = 0; - Length_High = 0; - Message_Block_Index = 0; - - H[0] = 0x67452301; - H[1] = 0xEFCDAB89; - H[2] = 0x98BADCFE; - H[3] = 0x10325476; - H[4] = 0xC3D2E1F0; - - Computed = false; - Corrupted = false; -} - -/* - * Result - * - * Description: - * This function will return the 160-bit message digest into the - * array provided. - * - * Parameters: - * message_digest_array: [out] - * This is an array of five unsigned integers which will be filled - * with the message digest that has been computed. - * - * Returns: - * True if successful, false if it failed. - * - * Comments: - * - */ -bool SHA1::Result(unsigned *message_digest_array) -{ - int i; // Counter - - if (Corrupted) - { - return false; - } - - if (!Computed) - { - PadMessage(); - Computed = true; - } - - for(i = 0; i < 5; i++) - { - message_digest_array[i] = H[i]; - } - - return true; -} - -/* - * Input - * - * Description: - * This function accepts an array of octets as the next portion of - * the message. - * - * Parameters: - * message_array: [in] - * An array of characters representing the next portion of the - * message. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input( const unsigned char *message_array, - unsigned length) -{ - if (!length) - { - return; - } - - if (Computed || Corrupted) - { - Corrupted = true; - return; - } - - while(length-- && !Corrupted) - { - Message_Block[Message_Block_Index++] = (*message_array & 0xFF); - - Length_Low += 8; - Length_Low &= 0xFFFFFFFF; // Force it to 32 bits - if (Length_Low == 0) - { - Length_High++; - Length_High &= 0xFFFFFFFF; // Force it to 32 bits - if (Length_High == 0) - { - Corrupted = true; // Message is too long - } - } - - if (Message_Block_Index == 64) - { - ProcessMessageBlock(); - } - - message_array++; - } -} - -/* - * Input - * - * Description: - * This function accepts an array of octets as the next portion of - * the message. - * - * Parameters: - * message_array: [in] - * An array of characters representing the next portion of the - * message. - * length: [in] - * The length of the message_array - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input( const char *message_array, - unsigned length) -{ - Input(reinterpret_cast<unsigned char *>(const_cast<char *>(message_array)), length); -} - -/* - * Input - * - * Description: - * This function accepts a single octets as the next message element. - * - * Parameters: - * message_element: [in] - * The next octet in the message. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input(unsigned char message_element) -{ - Input(&message_element, 1); -} - -/* - * Input - * - * Description: - * This function accepts a single octet as the next message element. - * - * Parameters: - * message_element: [in] - * The next octet in the message. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::Input(char message_element) -{ - Input((unsigned char *) &message_element, 1); -} - -/* - * operator<< - * - * Description: - * This operator makes it convenient to provide character strings to - * the SHA1 object for processing. - * - * Parameters: - * message_array: [in] - * The character array to take as input. - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * Each character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const char *message_array) -{ - const char *p = message_array; - - while(*p) - { - Input(*p); - p++; - } - - return *this; -} - -/* - * operator<< - * - * Description: - * This operator makes it convenient to provide character strings to - * the SHA1 object for processing. - * - * Parameters: - * message_array: [in] - * The character array to take as input. - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * Each character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const unsigned char *message_array) -{ - const unsigned char *p = message_array; - - while(*p) - { - Input(*p); - p++; - } - - return *this; -} - -/* - * operator<< - * - * Description: - * This function provides the next octet in the message. - * - * Parameters: - * message_element: [in] - * The next octet in the message - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * The character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const char message_element) -{ - Input(reinterpret_cast<unsigned char *>(const_cast<char *>(&message_element)), 1); - - return *this; -} - -/* - * operator<< - * - * Description: - * This function provides the next octet in the message. - * - * Parameters: - * message_element: [in] - * The next octet in the message - * - * Returns: - * A reference to the SHA1 object. - * - * Comments: - * The character is assumed to hold 8 bits of information. - * - */ -SHA1& SHA1::operator<<(const unsigned char message_element) -{ - Input(&message_element, 1); - - return *this; -} - -/* - * ProcessMessageBlock - * - * Description: - * This function will process the next 512 bits of the message - * stored in the Message_Block array. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * Many of the variable names in this function, especially the single - * character names, were used because those were the names used - * in the publication. - * - */ -void SHA1::ProcessMessageBlock() -{ - const unsigned K[] = { // Constants defined for SHA-1 - 0x5A827999, - 0x6ED9EBA1, - 0x8F1BBCDC, - 0xCA62C1D6 - }; - int t; // Loop counter - unsigned temp; // Temporary word value - unsigned W[80]; // Word sequence - unsigned A, B, C, D, E; // Word buffers - - /* - * Initialize the first 16 words in the array W - */ - for(t = 0; t < 16; t++) - { - W[t] = ((unsigned) Message_Block[t * 4]) << 24; - W[t] |= ((unsigned) Message_Block[t * 4 + 1]) << 16; - W[t] |= ((unsigned) Message_Block[t * 4 + 2]) << 8; - W[t] |= ((unsigned) Message_Block[t * 4 + 3]); - } - - for(t = 16; t < 80; t++) - { - W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); - } - - A = H[0]; - B = H[1]; - C = H[2]; - D = H[3]; - E = H[4]; - - for(t = 0; t < 20; t++) - { - temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 20; t < 40; t++) - { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 40; t < 60; t++) - { - temp = CircularShift(5,A) + - ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 60; t < 80; t++) - { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; - temp &= 0xFFFFFFFF; - E = D; - D = C; - C = CircularShift(30,B); - B = A; - A = temp; - } - - H[0] = (H[0] + A) & 0xFFFFFFFF; - H[1] = (H[1] + B) & 0xFFFFFFFF; - H[2] = (H[2] + C) & 0xFFFFFFFF; - H[3] = (H[3] + D) & 0xFFFFFFFF; - H[4] = (H[4] + E) & 0xFFFFFFFF; - - Message_Block_Index = 0; -} - -/* - * PadMessage - * - * Description: - * According to the standard, the message must be padded to an even - * 512 bits. The first padding bit must be a '1'. The last 64 bits - * represent the length of the original message. All bits in between - * should be 0. This function will pad the message according to those - * rules by filling the message_block array accordingly. It will also - * call ProcessMessageBlock() appropriately. When it returns, it - * can be assumed that the message digest has been computed. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * - */ -void SHA1::PadMessage() -{ - /* - * Check to see if the current message block is too small to hold - * the initial padding bits and length. If so, we will pad the - * block, process it, and then continue padding into a second block. - */ - if (Message_Block_Index > 55) - { - Message_Block[Message_Block_Index++] = 0x80; - while(Message_Block_Index < 64) - { - Message_Block[Message_Block_Index++] = 0; - } - - ProcessMessageBlock(); - - while(Message_Block_Index < 56) - { - Message_Block[Message_Block_Index++] = 0; - } - } - else - { - Message_Block[Message_Block_Index++] = 0x80; - while(Message_Block_Index < 56) - { - Message_Block[Message_Block_Index++] = 0; - } - - } - - /* - * Store the message length as the last 8 octets - */ - Message_Block[56] = static_cast<unsigned char>((Length_High >> 24) & 0xFF); - Message_Block[57] = static_cast<unsigned char>((Length_High >> 16) & 0xFF); - Message_Block[58] = static_cast<unsigned char>((Length_High >> 8) & 0xFF); - Message_Block[59] = static_cast<unsigned char>((Length_High) & 0xFF); - Message_Block[60] = static_cast<unsigned char>((Length_Low >> 24) & 0xFF); - Message_Block[61] = static_cast<unsigned char>((Length_Low >> 16) & 0xFF); - Message_Block[62] = static_cast<unsigned char>((Length_Low >> 8) & 0xFF); - Message_Block[63] = static_cast<unsigned char>((Length_Low) & 0xFF); - - ProcessMessageBlock(); -} - - -/* - * CircularShift - * - * Description: - * This member function will perform a circular shifting operation. - * - * Parameters: - * bits: [in] - * The number of bits to shift (1-31) - * word: [in] - * The value to shift (assumes a 32-bit integer) - * - * Returns: - * The shifted value. - * - * Comments: - * - */ -unsigned SHA1::CircularShift(int bits, unsigned word) -{ - return ((word << bits) & 0xFFFFFFFF) | ((word & 0xFFFFFFFF) >> (32-bits)); -} diff --git a/lib/sha1.h b/lib/sha1.h deleted file mode 100644 index 0a12d0f3..00000000 --- a/lib/sha1.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * sha1.h - * - * Copyright (C) 1998 - * Paul E. Jones <paulej@arid.us> - * All Rights Reserved. - * - ***************************************************************************** - * $Id: sha1.h,v 1.6 2004/03/27 18:02:26 paulej Exp $ - ***************************************************************************** - * - * Description: - * This class implements the Secure Hashing Standard as defined - * in FIPS PUB 180-1 published April 17, 1995. - * - * Many of the variable names in this class, especially the single - * character names, were used because those were the names used - * in the publication. - * - * Please read the file sha1.cpp for more information. - * - */ - -#ifndef _SHA1_H_ -#define _SHA1_H_ - -class SHA1 -{ - - public: - - SHA1(); - virtual ~SHA1(); - - /* - * Re-initialize the class - */ - void Reset(); - - /* - * Returns the message digest - */ - bool Result(unsigned *message_digest_array); - - /* - * Provide input to SHA1 - */ - void Input( const unsigned char *message_array, - unsigned length); - void Input( const char *message_array, - unsigned length); - void Input(unsigned char message_element); - void Input(char message_element); - SHA1& operator<<(const char *message_array); - SHA1& operator<<(const unsigned char *message_array); - SHA1& operator<<(const char message_element); - SHA1& operator<<(const unsigned char message_element); - - private: - - /* - * Process the next 512 bits of the message - */ - void ProcessMessageBlock(); - - /* - * Pads the current message block to 512 bits - */ - void PadMessage(); - - /* - * Performs a circular left shift operation - */ - inline unsigned CircularShift(int bits, unsigned word); - - unsigned H[5]; // Message digest buffers - - unsigned Length_Low; // Message length in bits - unsigned Length_High; // Message length in bits - - unsigned char Message_Block[64]; // 512-bit message blocks - int Message_Block_Index; // Index into message block array - - bool Computed; // Is the digest computed? - bool Corrupted; // Is the message digest corruped? - -}; - -#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6304a50c..a77422db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -254,7 +254,7 @@ include(GNUInstallDirs) if(BUILD_LIBRARY) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - add_library(libledger SHARED ${LEDGER_SOURCES} ${PROJECT_SOURCE_DIR}/lib/sha1.cpp) + add_library(libledger SHARED ${LEDGER_SOURCES}) add_ledger_library_dependencies(libledger) set_target_properties(libledger PROPERTIES PREFIX "" @@ -267,11 +267,9 @@ if(BUILD_LIBRARY) install(TARGETS libledger DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES ${LEDGER_INCLUDES} - ${PROJECT_SOURCE_DIR}/lib/sha1.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ledger) else() - add_executable(ledger - ${LEDGER_SOURCES} ${PROJECT_SOURCE_DIR}/lib/sha1.cpp main.cc global.cc) + add_executable(ledger ${LEDGER_SOURCES} main.cc global.cc) add_ledger_library_dependencies(ledger) endif() diff --git a/src/account.cc b/src/account.cc index 4b3a4e97..216b15bd 100644 --- a/src/account.cc +++ b/src/account.cc @@ -139,6 +139,36 @@ void account_t::add_post(post_t * post) } } +void account_t::add_deferred_post(const string& uuid, post_t * post) +{ + if (! deferred_posts) + deferred_posts = deferred_posts_map_t(); + + deferred_posts_map_t::iterator i = deferred_posts->find(uuid); + if (i == deferred_posts->end()) { + posts_list lst; + lst.push_back(post); + deferred_posts->insert(deferred_posts_map_t::value_type(uuid, lst)); + } else { + (*i).second.push_back(post); + } +} + +void account_t::apply_deferred_posts() +{ + if (deferred_posts) { + foreach (deferred_posts_map_t::value_type& pair, *deferred_posts) { + foreach (post_t * post, pair.second) + post->account->add_post(post); + } + deferred_posts = none; + } + + // Also apply in child accounts + foreach (const accounts_map::value_type& pair, accounts) + pair.second->apply_deferred_posts(); +} + bool account_t::remove_post(post_t * post) { // It's possible that 'post' wasn't yet in this account, but try to diff --git a/src/account.h b/src/account.h index f6f764cc..d1377c39 100644 --- a/src/account.h +++ b/src/account.h @@ -52,6 +52,7 @@ class post_t; typedef std::list<post_t *> posts_list; typedef std::map<string, account_t *> accounts_map; +typedef std::map<string, posts_list> deferred_posts_map_t; class account_t : public supports_flags<>, public scope_t { @@ -61,13 +62,14 @@ class account_t : public supports_flags<>, public scope_t #define ACCOUNT_GENERATED 0x04 // account never actually existed public: - account_t * parent; - string name; - optional<string> note; - unsigned short depth; - accounts_map accounts; - posts_list posts; - optional<expr_t> value_expr; + account_t * parent; + string name; + optional<string> note; + unsigned short depth; + accounts_map accounts; + posts_list posts; + optional<deferred_posts_map_t> deferred_posts; + optional<expr_t> value_expr; mutable string _fullname; #if DOCUMENT_MODEL @@ -136,6 +138,8 @@ public: } void add_post(post_t * post); + void add_deferred_post(const string& uuid, post_t * post); + void apply_deferred_posts(); bool remove_post(post_t * post); posts_list::iterator posts_begin() { diff --git a/src/filters.cc b/src/filters.cc index 4046ebda..bdc2983b 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -237,7 +237,7 @@ void anonymize_posts::render_commodity(amount_t& amt) void anonymize_posts::operator()(post_t& post) { - SHA1 sha; + boost::uuids::detail::sha1 sha; unsigned int message_digest[5]; bool copy_xact_details = false; @@ -256,9 +256,9 @@ void anonymize_posts::operator()(post_t& post) buf << reinterpret_cast<uintmax_t>(post.xact->payee.c_str()) << integer_gen() << post.xact->payee.c_str(); - sha.Reset(); - sha << buf.str().c_str(); - sha.Result(message_digest); + sha.reset(); + sha.process_bytes(buf.str().c_str(), buf.str().length()); + sha.get_digest(message_digest); xact.payee = to_hex(message_digest); xact.note = none; @@ -274,9 +274,9 @@ void anonymize_posts::operator()(post_t& post) std::ostringstream buf; buf << integer_gen() << acct << acct->fullname(); - sha.Reset(); - sha << buf.str().c_str(); - sha.Result(message_digest); + sha.reset(); + sha.process_bytes(buf.str().c_str(), buf.str().length()); + sha.get_digest(message_digest); account_names.push_front(to_hex(message_digest)); } diff --git a/src/journal.cc b/src/journal.cc index 160abe06..804c9ee2 100644 --- a/src/journal.cc +++ b/src/journal.cc @@ -95,7 +95,7 @@ void journal_t::initialize() force_checking = false; check_payees = false; day_break = false; - checking_style = CHECK_PERMISSIVE; + checking_style = CHECK_NORMAL; recursive_aliases = false; } @@ -363,6 +363,21 @@ namespace { } } +bool lt_posting_account(post_t * left, post_t * right) { + return left->account < right->account; +} + +bool is_equivalent_posting(post_t * left, post_t * right) +{ + if (left->account != right->account) + return false; + + if (left->amount != right->amount) + return false; + + return true; +} + bool journal_t::add_xact(xact_t * xact) { xact->journal = this; @@ -385,12 +400,54 @@ bool journal_t::add_xact(xact_t * xact) // will have been performed by extend_xact, so asserts can still be // applied to it. if (optional<value_t> ref = xact->get_tag(_("UUID"))) { + std::string uuid = ref->to_string(); std::pair<checksum_map_t::iterator, bool> result - = checksum_map.insert(checksum_map_t::value_type(ref->to_string(), xact)); + = checksum_map.insert(checksum_map_t::value_type(uuid, xact)); if (! result.second) { - // jww (2012-02-27): Confirm that the xact in - // (*result.first).second is exact match in its significant - // details to xact. + // This UUID has been seen before; apply any postings which the + // earlier version may have deferred. + foreach (post_t * post, xact->posts) { + account_t * acct = post->account; + if (acct->deferred_posts) { + auto i = acct->deferred_posts->find(uuid); + if (i != acct->deferred_posts->end()) { + for (post_t * rpost : (*i).second) + if (acct == rpost->account) + acct->add_post(rpost); + acct->deferred_posts->erase(i); + } + } + } + + xact_t * other = (*result.first).second; + + // Copy the two lists of postings (which should be relatively + // short), and make sure that the intersection is the empty set + // (i.e., that they are the same list). + std::vector<post_t *> this_posts(xact->posts.begin(), + xact->posts.end()); + std::sort(this_posts.begin(), this_posts.end(), + lt_posting_account); + std::vector<post_t *> other_posts(other->posts.begin(), + other->posts.end()); + std::sort(other_posts.begin(), other_posts.end(), + lt_posting_account); + bool match = std::equal(this_posts.begin(), this_posts.end(), + other_posts.begin(), is_equivalent_posting); + + if (! match || this_posts.size() != other_posts.size()) { + add_error_context(_("While comparing this previously seen transaction:")); + add_error_context(source_context(other->pos->pathname, + other->pos->beg_pos, + other->pos->end_pos, "> ")); + add_error_context(_("to this later transaction:")); + add_error_context(source_context(xact->pos->pathname, + xact->pos->beg_pos, + xact->pos->end_pos, "> ")); + throw_(std::runtime_error, + _f("Transactions with the same UUID must have equivalent postings")); + } + xact->journal = NULL; return false; } diff --git a/src/journal.h b/src/journal.h index e4763482..ad7b4b48 100644 --- a/src/journal.h +++ b/src/journal.h @@ -144,6 +144,7 @@ public: enum checking_style_t { CHECK_PERMISSIVE, + CHECK_NORMAL, CHECK_WARNING, CHECK_ERROR } checking_style; @@ -60,6 +60,7 @@ public: #define POST_COST_FIXATED 0x0200 // cost is fixed using = indicator #define POST_COST_VIRTUAL 0x0400 // cost is virtualized: (@) #define POST_ANONYMIZED 0x0800 // a temporary, anonymous posting +#define POST_DEFERRED 0x1000 // the account was specified with <angles> xact_t * xact; // only set for posts of regular xacts account_t * account; diff --git a/src/session.cc b/src/session.cc index b386607a..ec57eab3 100644 --- a/src/session.cc +++ b/src/session.cc @@ -106,10 +106,6 @@ std::size_t session_t::read_data(const string& master_account) } } - if (HANDLED(explicit)) - journal->force_checking = true; - if (HANDLED(check_payees)) - journal->check_payees = true; if (HANDLED(day_break)) journal->day_break = true; @@ -117,15 +113,21 @@ std::size_t session_t::read_data(const string& master_account) journal->recursive_aliases = true; if (HANDLED(no_aliases)) journal->no_aliases = true; - + + if (HANDLED(explicit)) + journal->force_checking = true; + if (HANDLED(check_payees)) + journal->check_payees = true; + if (HANDLED(permissive)) journal->checking_style = journal_t::CHECK_PERMISSIVE; else if (HANDLED(pedantic)) journal->checking_style = journal_t::CHECK_ERROR; else if (HANDLED(strict)) journal->checking_style = journal_t::CHECK_WARNING; - else if (HANDLED(value_expr_)) - journal->value_expr = HANDLER(value_expr_).str(); + + if (HANDLED(value_expr_)) + journal->value_expr = HANDLER(value_expr_).str(); #if HAVE_BOOST_SERIALIZATION optional<archive_t> cache; diff --git a/src/system.hh.in b/src/system.hh.in index fd5c47c6..8f501587 100644 --- a/src/system.hh.in +++ b/src/system.hh.in @@ -56,8 +56,6 @@ #define Ledger_VERSION_PATCH @Ledger_VERSION_PATCH@ #define Ledger_VERSION_DATE @Ledger_VERSION_DATE@ -#define HAVE_CXX11 @HAVE_CXX11@ - #define HAVE_EDIT @HAVE_EDIT@ #define HAVE_GETTEXT @HAVE_GETTEXT@ @@ -160,7 +158,6 @@ typedef std::ostream::pos_type ostream_pos_type; #include <gmp.h> #include <mpfr.h> -#include "sha1.h" #include "utf8.h" #if HAVE_EDIT diff --git a/src/textual.cc b/src/textual.cc index 627a1835..55f22a2c 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -77,16 +77,18 @@ namespace { std::istream& in; instance_t * parent; std::list<application_t> apply_stack; + bool no_assertions; #if defined(TIMELOG_SUPPORT) time_log_t timelog; #endif instance_t(parse_context_stack_t& _context_stack, parse_context_t& _context, - instance_t * _parent = NULL) + instance_t * _parent = NULL, + const bool _no_assertions = false) : context_stack(_context_stack), context(_context), in(*context.stream.get()), parent(_parent), - timelog(context) {} + no_assertions(_no_assertions), timelog(context) {} virtual string description() { return _("textual parser"); @@ -779,8 +781,8 @@ void instance_t::include_directive(char * line) context_stack.get_current().master = master; context_stack.get_current().scope = scope; try { - instance_t instance(context_stack, - context_stack.get_current(), this); + instance_t instance(context_stack, context_stack.get_current(), + this, no_assertions); instance.apply_stack.push_front(application_t("account", master)); instance.parse(); } @@ -1430,6 +1432,12 @@ post_t * instance_t::parse_post(char * line, } p++; e--; } + else if (*p == '<' && *(e - 1) == '>') { + post->add_flags(POST_DEFERRED); + DEBUG("textual.parse", "line " << context.linenum << ": " + << "Parsed a deferred account name"); + p++; e--; + } string name(p, static_cast<string::size_type>(e - p)); DEBUG("textual.parse", "line " << context.linenum << ": " @@ -1601,22 +1609,25 @@ post_t * instance_t::parse_post(char * line, "line " << context.linenum << ": " << "post amount = " << amt); amount_t diff = amt; + amount_t tot; switch (account_total.type()) { case value_t::AMOUNT: - diff -= account_total.as_amount(); + tot = account_total.as_amount(); break; case value_t::BALANCE: if (optional<amount_t> comm_bal = account_total.as_balance().commodity_amount(amt.commodity())) - diff -= *comm_bal; + tot = *comm_bal; break; default: break; } + diff -= tot; + DEBUG("post.assign", "line " << context.linenum << ": " << "diff = " << diff); DEBUG("textual.parse", "line " << context.linenum << ": " @@ -1625,8 +1636,10 @@ post_t * instance_t::parse_post(char * line, if (! diff.is_zero()) { if (! post->amount.is_null()) { diff -= post->amount; - if (! diff.is_zero()) - throw_(parse_error, _f("Balance assertion off by %1%") % diff); + if (! no_assertions && ! diff.is_zero()) + throw_(parse_error, + _f("Balance assertion off by %1% (expected to see %2%)") + % diff % tot); } else { post->amount = diff; DEBUG("textual.parse", "line " << context.linenum << ": " @@ -1909,13 +1922,17 @@ std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); { - instance_t instance(context_stack, context_stack.get_current()); + instance_t instance(context_stack, context_stack.get_current(), NULL, + checking_style == journal_t::CHECK_PERMISSIVE); instance.apply_stack.push_front (application_t("account", context_stack.get_current().master)); instance.parse(); } TRACE_STOP(parsing_total, 1); + // Apply any deferred postings at this time + master->apply_deferred_posts(); + // These tracers were started in textual.cc TRACE_FINISH(xact_text, 1); TRACE_FINISH(xact_details, 1); diff --git a/src/utils.h b/src/utils.h index 70b3bae9..c3dcf562 100644 --- a/src/utils.h +++ b/src/utils.h @@ -44,6 +44,8 @@ #ifndef _UTILS_H #define _UTILS_H +#include <boost/uuid/sha1.hpp> + /** * @name Default values */ @@ -483,11 +485,7 @@ inline void check_for_signal() { /*@{*/ #define foreach BOOST_FOREACH -#if HAVE_CXX11 using std::unique_ptr; -#else -#define unique_ptr std::auto_ptr -#endif namespace ledger { @@ -625,11 +623,12 @@ inline string to_hex(unsigned int * message_digest, const int len = 1) inline string sha1sum(const string& str) { - SHA1 sha; - sha.Reset(); - sha << str.c_str(); + boost::uuids::detail::sha1 sha; + + sha.process_bytes(str.c_str(), str.length()); + unsigned int message_digest[5]; - sha.Result(message_digest); + sha.get_digest(message_digest); return to_hex(message_digest, 5); } diff --git a/src/xact.cc b/src/xact.cc index 58d2ac33..c7a9c6e3 100644 --- a/src/xact.cc +++ b/src/xact.cc @@ -393,7 +393,10 @@ bool xact_base_t::finalize() some_null = true; } - post->account->add_post(post); + if (post->has_flags(POST_DEFERRED)) + 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); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 796ef0a2..97da8b24 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,16 +38,17 @@ add_subdirectory(manual) add_subdirectory(baseline) add_subdirectory(regress) -if(PYTHONINTERP_FOUND) - set(_class DocTests) - file(GLOB ${_class}_TESTS ${PROJECT_SOURCE_DIR}/doc/*.texi) - foreach(TestFile ${${_class}_TESTS}) - get_filename_component(TestFile_Name ${TestFile} NAME_WE) - add_test(${_class}Test_${TestFile_Name} - ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/DocTests.py - --ledger ${LEDGER_LOCATION} --file ${TestFile}) - set_target_properties(check PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) - endforeach() -endif() +# jww (2014-04-17): This is temporary until we find a fix. +#if(PYTHONINTERP_FOUND) +# set(_class DocTests) +# file(GLOB ${_class}_TESTS ${PROJECT_SOURCE_DIR}/doc/*.texi) +# foreach(TestFile ${${_class}_TESTS}) +# get_filename_component(TestFile_Name ${TestFile} NAME_WE) +# add_test(${_class}Test_${TestFile_Name} +# ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/DocTests.py +# --ledger ${LEDGER_LOCATION} --file ${TestFile}) +# set_target_properties(check PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) +# endforeach() +#endif() ### CMakeLists.txt ends here diff --git a/test/baseline/feat-balance_assert_split.test b/test/baseline/feat-balance_assert_split.test new file mode 100644 index 00000000..2d9cce9f --- /dev/null +++ b/test/baseline/feat-balance_assert_split.test @@ -0,0 +1,60 @@ +;; a.dat + +2012-01-01 Test + Expenses:Unknown $100.00 + Liabilities:MasterCard + +2012-01-02 Test + Expenses:Unknown $100.00 + Liabilities:MasterCard + +2012-01-03 Test + Expenses:Unknown $100.00 + Liabilities:MasterCard + +2012-01-04 Test + ; UUID: foo + Liabilities:MasterCard $150.00 = $-300 + <Assets:Checking> + +2012-01-04 Test + ; UUID: bar + Liabilities:MasterCard $150.00 = $0 + <Assets:Checking> + +2012-01-04 Test + ; UUID: baz + Liabilities:MasterCard $150.00 = $0 + <Assets:Checking> + +;; b.dat + +2012-01-01 Test + Assets:Checking $150.00 + Income + +2012-01-02 Test + Assets:Checking $150.00 + Income + +2012-01-03 Test + Assets:Checking $150.00 + Income + +2012-01-04 Test + ; UUID: foo + Liabilities:MasterCard $150.00 + Assets:Checking $-150.00 = $300.00 + +2012-01-04 Test + ; UUID: bar + Liabilities:MasterCard $150.00 + Assets:Checking $-150.00 = $150.00 + +test balance + $300.00 Expenses:Unknown + $-450.00 Income + $150.00 Liabilities:MasterCard +-------------------- + 0 +end test diff --git a/tools/build.sh b/tools/build.sh index ba563e34..b7a80587 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -4,8 +4,8 @@ flavor=$1 shift 1 JOBS=-j$(sysctl -n hw.activecpu) -OPTIONS="$flavor --debug --python --ninja --doxygen $JOBS" -OPTIONS="$OPTIONS --prefix /usr/local/Cellar/ledger/HEAD" +#OPTIONS="$flavor --debug --python --ninja --doxygen $JOBS" +OPTIONS="$flavor --debug --ninja $JOBS" time ( \ cd ~/src/ledger ; \ |