diff options
-rw-r--r-- | NEWS | 105 | ||||
-rw-r--r-- | datetime.cc | 73 | ||||
-rw-r--r-- | datetime.h | 16 | ||||
-rw-r--r-- | debug.h | 8 | ||||
-rw-r--r-- | error.h | 13 | ||||
-rw-r--r-- | format.cc | 9 | ||||
-rw-r--r-- | format.h | 17 | ||||
-rw-r--r-- | main.cc | 177 | ||||
-rwxr-xr-x | scripts/confirm.py | 4 | ||||
-rwxr-xr-x | scripts/test | 6 | ||||
-rw-r--r-- | valexpr.cc | 60 | ||||
-rw-r--r-- | valexpr.h | 2 | ||||
-rw-r--r-- | walk.cc | 64 | ||||
-rw-r--r-- | walk.h | 44 |
14 files changed, 403 insertions, 195 deletions
@@ -3,30 +3,39 @@ * 2.0 -- The code base has been rewritten for clarity and consistency. As a - result, the code is much simpler and more robust (and in most cases - faster). - -- Register reports now show the account being credited/debited. Use - new -o option to see "other accounts" -- or the account the - credit/debit came from. (This was the old behavior in 1.x, but can - lead to confusing reports when viewing accounts with subaccounts.) - The -o option also works for balance reports, where it will show all - the account totals related to your query. - -- Regexps specified after the command are applied to account names - only. To search on a payee name, use "--" to separate the two kinds - of regexps. For example, to find payee's named John within all - Expenses accounts: +- The code base has been rewritten for clarity and consistency. The + code is now simpler, more robust, and a fair bit faster. - ledger register expenses -- john +- The most significant feature addition in this version is the use of + "value expressions". These are now used in many places to indicate + what to display, the sorting order, and even the output format. -- The use of "+" and "-" in ledger files (to specify permanent - regexps) has been removed. + A value expression is a simple string that uses one letter codes to + indicate transaction, entry and account details. Logic and math + operators are supported, as well as a few useful functions. See the + README. -- The -G switch no longer generates gnuplot-safe data. It now reports - totals in terms of net gain/loss. +- If the environment variable LEDGER is used, a binary cache of that + current ledger will be kept, to speed up later queries of the same + data. The default location is in ~/.ledger, but this can be changed + with the environment variable LEDGER_CACHE. This only happens if no + "-f" flag was seen (i.e., if the LEDGER environment variable is + used). + +- New "-y DATEFMT" options will change the date format used throughout + ledger. The default is "%Y/%m/%d". + +- Regexps specified after the command name now apply to account names + only. To search on a payee, use "--" to separate the two kinds of + regexps. For example, to find a payee named "John" within all + Expenses accounts, use: + ledger register expenses -- john + + FYI: The above command is identical (and internally converted) to: + + ledger -l "/expenses/|//john/" register + - To include entries from a file into a specific account, use: @ ACCOUNT @@ -36,10 +45,12 @@ All entries specified within the "@ ACCOUNT/@@" range will be added under that account. -- If the environment variable LEDGER_CACHE is set to a filename, a a - binary dump of the current ledger will be written then, to speed up - later queries of the same data. This only happens if no "-f" flag - was seen (i.e., if the LEDGER environment variable is used). +- Register reports now show the account being changed. Use the -r + option to see "other accounts" -- or the account the credit/debit + came from. (This was the old behavior in 1.x, but can lead to + confusing reports when viewing accounts with subaccounts.) The -r + option also works for balance reports, where it will show all the + account totals related to your query. - There are several new default reporting styles, which work both in the balance and register reports: @@ -54,6 +65,32 @@ -W Report the trend, with older values affecting the trend less -X Report expected amount for the next transaction +- Automated transactions now use a single value expression as a + predicate. This means the new syntax is: + + = VALUE-EXPR + TRANSACTIONS... + + Only one VALUE-EXPR is supported, compared to the multiple account + regexps supported before. By using a VALUE-EXPR as a predicate, + matching may now be much more comprehensive and selective. + +- The use of "+" and "-" in ledger files (to specify permanent + regexps) has been removed. + +- The -G switch no longer generates gnuplot-safe data. It now reports + totals in terms of net gain/loss. + +- The -l flag now takes an expression string as a "predicate". + Therefore, to equal the old behavior of "-l $100", you would use: + + -l AT<{$100} + +- The -S flag now takes an expression string, which yields the value + that will be sorted on. + +---------------------------------------------------------------------- + - Value expressions are now supported, where the totals reported can be changed using -t and -T and an expression string composed of: @@ -101,14 +138,6 @@ -W == -t a -T MD(MA*(d-b/e-b)) -X == -t a -T a+MD(MA*(d-b/e-b)) -- The -l flag now takes an expression string as a "predicate". - Therefore, to equal the old behavior of "-l $100", you would use: - - -l AT<{$100} - -- The -S flag now takes an expression string, which yields the value - that will be sorted on. - - User-specified format strings are supported with a -F option. The strings are very much like printf format strings, with the following syntax for each substitution: @@ -142,17 +171,7 @@ %?10d %?-.20p %-.22a %12.66t %12.80T %20T %-a -- Automated transactions now use a single value expression as a - predicate. This means the new syntax is: - - = VALUE-EXPR - TRANSACTIONS... - - Only one VALUE-EXPR is supported, compared to the multiple account - regexps supported before. By using a VALUE-EXPR as a predicate, - matching may now be much more comprehensive and selective. - -* 1.7 (never released) +* 1.7 - Pricing histories are now supported, so that ledger remembers historical pricing of all commodities, and can give register reports diff --git a/datetime.cc b/datetime.cc index 71393ebf..a2badd39 100644 --- a/datetime.cc +++ b/datetime.cc @@ -31,8 +31,77 @@ static const char * formats[] = { std::time_t interval_t::increment(const std::time_t moment) { - // jww (2004-08-11): NYI - return moment + seconds; + std::time_t then = moment; + + if (years || months) { + struct std::tm * desc = std::gmtime(&then); + if (years) + desc->tm_year += years; + if (months) { + desc->tm_mon += months; + + if (desc->tm_mon > 11) { + desc->tm_year++; + desc->tm_mon -= 12; + } + } + then = std::mktime(desc); + } + + return then + seconds; +} + +interval_t * interval_t::parse(std::istream& in) +{ + unsigned long years = 0; + unsigned long months = 0; + unsigned long seconds = 0; + + std::string word; + in >> word; + if (word == "every") { + in >> word; + if (std::isdigit(word[0])) { + int quantity = std::atol(word.c_str()); + in >> word; + if (word == "days") + seconds = 86400 * quantity; + else if (word == "weeks") + seconds = 7 * 86400 * quantity; + else if (word == "months") + months = quantity; + else if (word == "quarters") + months = 3 * quantity; + else if (word == "years") + years = quantity; + } + else if (word == "day") + seconds = 86400; + else if (word == "week") + seconds = 7 * 86400; + else if (word == "monthly") + months = 1; + else if (word == "quarter") + months = 3; + else if (word == "year") + years = 1; + } + else if (word == "daily") + seconds = 86400; + else if (word == "weekly") + seconds = 7 * 86400; + else if (word == "biweekly") + seconds = 14 * 86400; + else if (word == "monthly") + months = 1; + else if (word == "bimonthly") + months = 2; + else if (word == "quarterly") + months = 3; + else if (word == "yearly") + years = 1; + + return new interval_t(seconds, months, years); } bool parse_date_mask(const char * date_str, struct std::tm * result) @@ -11,21 +11,23 @@ struct interval_t unsigned long months; unsigned long seconds; - interval_t(unsigned long _seconds, unsigned long _months, - unsigned long _years) + interval_t(unsigned long _seconds, unsigned long _months = 0, + unsigned long _years = 0) : years(_years), months(_months), seconds(_seconds) {} std::time_t increment(const std::time_t); + + static interval_t * parse(std::istream& in); }; -extern bool parse_date_mask(const char * date_str, struct std::tm * result); +extern struct std::tm * now_tm; -extern bool parse_date(const char * date_str, std::time_t * result, - const int year = -1); +bool parse_date_mask(const char * date_str, struct std::tm * result); -extern bool quick_parse_date(char * date_str, std::time_t * result); +bool parse_date(const char * date_str, std::time_t * result, + const int year = -1); -extern struct std::tm * now_tm; +bool quick_parse_date(char * date_str, std::time_t * result); } // namespace ledger @@ -81,6 +81,14 @@ inline bool _debug_active(const char * const cls) { } #define DEBUG_PRINT_(x) DEBUG_PRINT(_debug_cls, x) +#define DEBUG_PRINT_TIME(cls, x) { \ + char buf[256]; \ + std::strftime(buf, 255, "%Y/%m/%d", std::gmtime(&x)); \ + DEBUG_PRINT(cls, #x << " is " << buf); \ +} + +#define DEBUG_PRINT_TIME_(x) DEBUG_PRINT_TIME(_debug_cls, x) + #define CONFIRM(x) assert(x) #if RELEASE_LEVEL == DEVELOPER @@ -27,11 +27,18 @@ class compute_error : public error virtual ~compute_error() throw() {} }; -class expr_error : public error +class value_expr_error : public error { public: - expr_error(const std::string& reason) throw() : error(reason) {} - virtual ~expr_error() throw() {} + value_expr_error(const std::string& reason) throw() : error(reason) {} + virtual ~value_expr_error() throw() {} +}; + +class interval_expr_error : public error +{ + public: + interval_expr_error(const std::string& reason) throw() : error(reason) {} + virtual ~interval_expr_error() throw() {} }; class format_error : public error @@ -33,6 +33,8 @@ std::string partial_account_name(const account_t * account) return name; } +std::string format_t::date_format = "%Y/%m/%d"; + std::auto_ptr<value_expr_t> format_t::value_expr; std::auto_ptr<value_expr_t> format_t::total_expr; @@ -114,15 +116,14 @@ element_t * format_t::parse_elements(const std::string& fmt) current->chars = num; break; - case 'd': + case 'D': current->type = element_t::DATE_STRING; - // jww (2004-08-10): allow this to be changed - current->chars = "%Y/%m/%d"; + current->chars = format_t::date_format; break; case 'X': current->type = element_t::CLEARED; break; case 'C': current->type = element_t::CODE; break; - case 'p': current->type = element_t::PAYEE; break; + case 'P': current->type = element_t::PAYEE; break; case 'n': current->type = element_t::ACCOUNT_NAME; break; case 'N': current->type = element_t::ACCOUNT_FULLNAME; break; case 'o': current->type = element_t::OPT_AMOUNT; break; @@ -52,6 +52,8 @@ struct format_t { element_t * elements; + static std::string date_format; + static std::auto_ptr<value_expr_t> value_expr; static std::auto_ptr<value_expr_t> total_expr; @@ -107,14 +109,15 @@ class format_transactions : public item_handler<transaction_t> } virtual void operator()(transaction_t * xact) { - if (last_entry != xact->entry) { - first_line_format.format_elements(output_stream, details_t(xact)); - last_entry = xact->entry; - } else { - next_lines_format.format_elements(output_stream, details_t(xact)); + if (! (xact->dflags & TRANSACTION_DISPLAYED)) { + if (last_entry != xact->entry) { + first_line_format.format_elements(output_stream, details_t(xact)); + last_entry = xact->entry; + } else { + next_lines_format.format_elements(output_stream, details_t(xact)); + } + xact->dflags |= TRANSACTION_DISPLAYED; } - - xact->dflags |= TRANSACTION_DISPLAYED; } }; @@ -12,39 +12,13 @@ namespace ledger { static const std::string bal_fmt = "%20T %2_%-n\n"; - static const std::string reg_fmt - = "%10d %-.20p %-.22N %12.66t %12.80T\n\ + = "%D %-.20P %-.22N %12.66t %12.80T\n\ %/ %-.22N %12.66t %12.80T\n"; - static const std::string print_fmt - = "\n%10d %X%C%p\n %-34N %12o\n%/ %-34N %12o\n"; - + = "\n%D %X%C%P\n %-34N %12o\n%/ %-34N %12o\n"; static const std::string equity_fmt - = "\n%10d %X%C%p\n%/ %-34N %12t\n"; - - -void set_price_conversion(const std::string& setting) -{ - char buf[128]; - std::strcpy(buf, setting.c_str()); - - assert(setting.length() < 128); - - char * c = buf; - char * p = std::strchr(buf, '='); - if (! p) { - std::cerr << "Warning: Invalid price setting: " << setting << std::endl; - } else { - *p++ = '\0'; - - amount_t price; - price.parse(p); - - commodity_t * commodity = commodity_t::find_commodity(c, true); - commodity->set_conversion(price); - } -} + = "\n%D %X%C%P\n%/ %-34N %12t\n"; static long pricing_leeway = 24 * 3600; @@ -102,6 +76,20 @@ void download_price_quote(commodity_t * commodity, } // namespace ledger +static std::string ledger_cache_file() +{ + std::string cache_file; + + if (const char * p = std::getenv("LEDGER_CACHE")) { + cache_file = p; + } + else if (const char * p = std::getenv("HOME")) { + cache_file = p; + cache_file += "/.ledger"; + } + return cache_file; +} + static void show_version(std::ostream& out) { out @@ -157,9 +145,13 @@ int main(int argc, char * argv[]) { using namespace ledger; - std::auto_ptr<journal_t> journal(new journal_t); - std::list<std::string> files; - std::auto_ptr<value_expr_t> sort_order; + std::auto_ptr<journal_t> journal(new journal_t); + std::list<std::string> files; + std::auto_ptr<value_expr_t> sort_order; + std::auto_ptr<std::ostream> output_stream; + std::auto_ptr<interval_t> report_interval; + +#define OUT() (output_stream.get() ? *output_stream.get() : std::cout) std::string predicate; std::string display_predicate; @@ -167,6 +159,7 @@ int main(int argc, char * argv[]) std::string sort_string; std::string value_expr = "a"; std::string total_expr = "T"; + std::time_t interval_begin = 0; bool show_subtotals = true; bool show_expanded = false; @@ -205,18 +198,20 @@ int main(int argc, char * argv[]) cache_dirty = true; - if (use_cache) - if (const char * p = std::getenv("LEDGER_CACHE")) - if (access(p, R_OK) != -1) { - std::ifstream instr(p); - if (! read_binary_journal(instr, std::getenv("LEDGER"), - journal.get())) { - // Throw away what's been read, and create a new journal - journal.reset(new journal_t); - } else { - cache_dirty = false; - } + if (use_cache) { + std::string cache_file = ledger_cache_file(); + if (! cache_file.empty() && + access(cache_file.c_str(), R_OK) != -1) { + std::ifstream stream(cache_file.c_str()); + if (! read_binary_journal(stream, std::getenv("LEDGER"), + journal.get())) { + // Throw away what's been read, and create a new journal + journal.reset(new journal_t); + } else { + cache_dirty = false; } + } + } } // Parse the command-line options @@ -224,7 +219,7 @@ int main(int argc, char * argv[]) int c, index; while (-1 != (c = getopt(argc, argv, - "+ABb:Ccd:DEe:F:f:Ghi:L:l:MnoOP:p:QRS:st:T:UVvWXZ"))) { + "+ABb:Ccd:DEe:F:f:Ghi:L:l:MnOo:P:p:QRrS:sT:t:UVvWXYy:Zz:"))) { switch (char(c)) { // Basic options case 'h': @@ -240,8 +235,19 @@ int main(int argc, char * argv[]) use_cache = false; break; + case 'o': + if (std::string(optarg) != "-") + output_stream.reset(new std::ofstream(optarg)); + break; + case 'p': - set_price_conversion(optarg); + if (char * p = std::strchr(optarg, '=')) { + *p = ' '; + std::string conversion = "C "; + conversion += p; + std::istringstream stream(conversion); + parse_textual_journal(stream, journal.get(), journal->master); + } break; case 'b': @@ -294,6 +300,10 @@ int main(int argc, char * argv[]) format_string = optarg; break; + case 'y': + format_t::date_format = optarg; + break; + case 'E': show_empty = true; break; @@ -310,10 +320,39 @@ int main(int argc, char * argv[]) sort_string = optarg; break; - case 'o': + case 'r': show_related = true; break; + case 'z': { + std::string str(optarg); + std::istringstream stream(str); + report_interval.reset(interval_t::parse(stream)); + + if (! stream.eof()) { + std::string word; + stream >> word; + if (word == "from") { + stream >> word; + if (! parse_date(word.c_str(), &interval_begin)) + throw interval_expr_error("Could not parse 'from' date"); + } + } + break; + } + + case 'W': + report_interval.reset(new interval_t(604800, 0, 0)); + break; + + case 'M': + report_interval.reset(new interval_t(0, 1, 0)); + break; + + case 'Y': + report_interval.reset(new interval_t(0, 0, 1)); + break; + case 'l': if (! predicate.empty()) predicate += "&"; @@ -386,22 +425,15 @@ int main(int argc, char * argv[]) total_expr = "DMT"; break; - case 'Z': + case 'X': value_expr = "a"; total_expr = "MDMT"; break; -#if 0 - case 'W': - value_expr = "a"; - total_expr = "MD(MT*(d-b/e-b))"; - break; - - case 'X': + case 'Z': value_expr = "a"; - total_expr = "a+MD(MT*(d-b/e-b))"; + total_expr = "MD(MT/(1+(((t-d)/(30*86400))<0?0:((t-d)/(30*86400)))))"; break; -#endif default: assert(0); @@ -444,7 +476,7 @@ int main(int argc, char * argv[]) std::ifstream db(path); journal->sources.push_back(path); entry_count += parse_textual_journal(db, journal.get(), - journal->master); + journal->master); } } catch (error& err) { @@ -597,7 +629,7 @@ int main(int argc, char * argv[]) formatter.reset(new filter_transactions(formatter.release(), predicate)); walk_entries(journal->entries, *formatter.get()); - format_account acct_formatter(std::cout, format, display_predicate); + format_account acct_formatter(OUT(), format, display_predicate); if (show_subtotals) sum_accounts(journal->master); walk_accounts(journal->master, acct_formatter, sort_order.get()); @@ -605,7 +637,7 @@ int main(int argc, char * argv[]) if (format_account::disp_subaccounts_p(journal->master)) { std::string end_format = "--------------------\n"; format.reset(end_format + f); - format.format_elements(std::cout, details_t(journal->master)); + format.format_elements(OUT(), details_t(journal->master)); } } else if (command == "E") { @@ -614,12 +646,12 @@ int main(int argc, char * argv[]) formatter.reset(new filter_transactions(formatter.release(), predicate)); walk_entries(journal->entries, *formatter.get()); - format_equity acct_formatter(std::cout, format, nformat, display_predicate); + format_equity acct_formatter(OUT(), format, nformat, display_predicate); sum_accounts(journal->master); walk_accounts(journal->master, acct_formatter, sort_order.get()); } else if (command == "e") { - format_transactions formatter(std::cout, format, nformat); + format_transactions formatter(OUT(), format, nformat); walk_transactions(new_entry->transactions, formatter); } else { @@ -631,7 +663,7 @@ int main(int argc, char * argv[]) // format_transactions write each transaction received to the // output stream. - formatter.reset(new format_transactions(std::cout, format, nformat)); + formatter.reset(new format_transactions(OUT(), format, nformat)); // sort_transactions will sort all the transactions it sees, based // on the `sort_order' value expression. @@ -653,8 +685,8 @@ int main(int argc, char * argv[]) // list to account for changes in market value of commodities, // which otherwise would affect the running total unpredictably. if (show_revalued) - formatter.reset(new changed_value_transactions(formatter.release() /*, - show_revalued_only*/)); + formatter.reset(new changed_value_transactions(formatter.release(), + show_revalued_only)); // collapse_transactions causes entries with multiple transactions // to appear as entries with a subtotaled transaction for each @@ -671,9 +703,10 @@ int main(int argc, char * argv[]) // everything. if (show_expanded) formatter.reset(new subtotal_transactions(formatter.release())); - else if (0) - formatter.reset(new interval_transactions(formatter.release(), 0, - interval_t(9676800, 0, 0))); + else if (report_interval.get()) + formatter.reset(new interval_transactions(formatter.release(), + *report_interval.get(), + interval_begin)); // related_transactions will pass along all transactions related // to the transaction received. If `show_all_related' is true, @@ -703,12 +736,14 @@ int main(int argc, char * argv[]) // Save the cache, if need be - if (use_cache && cache_dirty) - if (const char * p = std::getenv("LEDGER_CACHE")) { - std::ofstream outstr(p); + if (use_cache && cache_dirty) { + std::string cache_file = ledger_cache_file(); + if (! cache_file.empty()) { assert(std::getenv("LEDGER")); - write_binary_journal(outstr, journal.get(), std::getenv("LEDGER")); + std::ofstream stream(cache_file.c_str()); + write_binary_journal(stream, journal.get(), std::getenv("LEDGER")); } + } return 0; } diff --git a/scripts/confirm.py b/scripts/confirm.py index c3fe70ab..ea39e94b 100755 --- a/scripts/confirm.py +++ b/scripts/confirm.py @@ -13,7 +13,7 @@ running_total = 0.0 index = 1 last_line = "" -for line in os.popen("./ledger %s reg %s" % (sys.argv[1], sys.argv[2])): +for line in os.popen("../ledger %s reg %s" % (sys.argv[1], sys.argv[2])): value = clean(line[55:67]) total = clean(line[68:80]) @@ -29,7 +29,7 @@ for line in os.popen("./ledger %s reg %s" % (sys.argv[1], sys.argv[2])): balance_total = 0.0 -for line in os.popen("./ledger %s bal %s" % (sys.argv[1], sys.argv[2])): +for line in os.popen("../ledger %s bal %s" % (sys.argv[1], sys.argv[2])): balance_total = clean(line[:20]) if abs(balance_total - running_total) > 0.001: diff --git a/scripts/test b/scripts/test index 7b17a1b2..09b0bb03 100755 --- a/scripts/test +++ b/scripts/test @@ -2,9 +2,9 @@ for test in \ "-O nrl:checking" \ - "-B 401" \ - "-V 401" \ - "-G 401" \ + "-B 401" \ + "-V 401" \ + "-G 401" \ "-B retire" \ "-V retire" \ "-G retire" @@ -125,6 +125,10 @@ void value_expr_t::compute(balance_t& result, const details_t& details) const result = (unsigned int) std::time(NULL); break; + case TODAY: + result = (unsigned int) std::time(NULL); + break; + case CLEARED: if (details.entry) { result = details.entry->state == entry_t::CLEARED; @@ -188,6 +192,15 @@ void value_expr_t::compute(balance_t& result, const details_t& details) const result = abs(result); break; + case F_STRIP: { + assert(left); + left->compute(result, details); + amount_t amt = result.amount(); + amt.commodity = commodity_t::null_commodity; + result = amt; + break; + } + case F_PAYEE_MASK: assert(mask); if (details.entry) @@ -214,6 +227,10 @@ void value_expr_t::compute(balance_t& result, const details_t& details) const moment = std::time(NULL); break; + case TODAY: + moment = std::time(NULL); + break; + default: throw compute_error("Invalid date passed to P(value,date)"); } @@ -341,7 +358,7 @@ value_expr_t * parse_value_term(std::istream& in) if (c == '}') in.get(c); else - throw expr_error("Missing '}'"); + throw value_expr_error("Missing '}'"); } else { while (! in.eof() && std::isdigit(c) || c == '.') { in.get(c); @@ -363,6 +380,7 @@ value_expr_t * parse_value_term(std::istream& in) case 'a': node = new value_expr_t(value_expr_t::AMOUNT); break; case 'c': node = new value_expr_t(value_expr_t::COST); break; case 'd': node = new value_expr_t(value_expr_t::DATE); break; + case 't': node = new value_expr_t(value_expr_t::TODAY); break; case 'X': node = new value_expr_t(value_expr_t::CLEARED); break; case 'R': node = new value_expr_t(value_expr_t::REAL); break; case 'n': node = new value_expr_t(value_expr_t::INDEX); break; @@ -389,6 +407,11 @@ value_expr_t * parse_value_term(std::istream& in) node->left = parse_value_term(in); break; + case 'S': + node = new value_expr_t(value_expr_t::F_STRIP); + node->left = parse_value_term(in); + break; + case 'M': node = new value_expr_t(value_expr_t::F_ARITH_MEAN); node->left = parse_value_term(in); @@ -413,7 +436,7 @@ value_expr_t * parse_value_term(std::istream& in) if (peek_next_nonws(in) == ')') in.get(c); else - throw expr_error("Missing ')'"); + throw value_expr_error("Missing ')'"); } else { node->left = parse_value_term(in); } @@ -445,7 +468,7 @@ value_expr_t * parse_value_term(std::istream& in) value_expr_t::F_PAYEE_MASK : value_expr_t::F_ACCOUNT_MASK); node->mask = new mask_t(ident); } else { - throw expr_error("Missing closing '/'"); + throw value_expr_error("Missing closing '/'"); } break; } @@ -455,7 +478,7 @@ value_expr_t * parse_value_term(std::istream& in) if (peek_next_nonws(in) == ')') in.get(c); else - throw expr_error("Missing ')'"); + throw value_expr_error("Missing ')'"); break; case '[': { @@ -471,9 +494,9 @@ value_expr_t * parse_value_term(std::istream& in) in.get(c); node = new value_expr_t(value_expr_t::CONSTANT_T); if (! parse_date(ident.c_str(), &node->constant_t)) - throw expr_error("Failed to parse date"); + throw value_expr_error("Failed to parse date"); } else { - throw expr_error("Missing ']'"); + throw value_expr_error("Missing ']'"); } break; } @@ -609,7 +632,7 @@ value_expr_t * parse_logic_expr(std::istream& in) if (! in.eof()) { std::ostringstream err; err << "Unexpected character '" << c << "'"; - throw expr_error(err.str()); + throw value_expr_error(err.str()); } } } @@ -656,7 +679,7 @@ value_expr_t * parse_value_expr(std::istream& in) if (c != ':') { std::ostringstream err; err << "Unexpected character '" << c << "'"; - throw expr_error(err.str()); + throw value_expr_error(err.str()); } in.get(c); choices->right = parse_logic_expr(in); @@ -667,7 +690,7 @@ value_expr_t * parse_value_expr(std::istream& in) if (! in.eof()) { std::ostringstream err; err << "Unexpected character '" << c << "'"; - throw expr_error(err.str()); + throw value_expr_error(err.str()); } } c = peek_next_nonws(in); @@ -730,12 +753,13 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node) out << "DATE/TIME[" << node->constant_t << "]"; break; - case value_expr_t::AMOUNT: out << "AMOUNT"; break; - case value_expr_t::COST: out << "COST"; break; - case value_expr_t::DATE: out << "DATE"; break; - case value_expr_t::CLEARED: out << "CLEARED"; break; - case value_expr_t::REAL: out << "REAL"; break; - case value_expr_t::INDEX: out << "INDEX"; break; + case value_expr_t::AMOUNT: out << "AMOUNT"; break; + case value_expr_t::COST: out << "COST"; break; + case value_expr_t::DATE: out << "DATE"; break; + case value_expr_t::TODAY: out << "TODAY"; break; + case value_expr_t::CLEARED: out << "CLEARED"; break; + case value_expr_t::REAL: out << "REAL"; break; + case value_expr_t::INDEX: out << "INDEX"; break; case value_expr_t::BALANCE: out << "BALANCE"; break; case value_expr_t::COST_BALANCE: out << "COST_BALANCE"; break; case value_expr_t::TOTAL: out << "TOTAL"; break; @@ -759,6 +783,12 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node) out << ")"; break; + case value_expr_t::F_STRIP: + out << "STRIP("; + dump_value_expr(out, node->left); + out << ")"; + break; + case value_expr_t::F_PAYEE_MASK: assert(node->mask); out << "P_MASK(" << node->mask->pattern << ")"; @@ -48,6 +48,7 @@ struct value_expr_t AMOUNT, COST, DATE, + TODAY, CLEARED, REAL, INDEX, // for accounts, this is the DEPTH @@ -63,6 +64,7 @@ struct value_expr_t F_VALUE, F_NEG, F_ABS, + F_STRIP, F_PAYEE_MASK, F_ACCOUNT_MASK, @@ -3,6 +3,21 @@ namespace ledger { +void sort_transactions::flush() +{ + std::stable_sort(transactions.begin(), transactions.end(), + compare_items<transaction_t>(sort_order)); + + for (transactions_deque::iterator i = transactions.begin(); + i != transactions.end(); + i++) + (*handler)(*i); + + transactions.clear(); + + handler->flush(); +} + void calc_transactions::operator()(transaction_t * xact) { if (last_xact) @@ -92,8 +107,12 @@ void changed_value_transactions::operator()(transaction_t * xact) } } - if (xact) + if (xact) { + if (changed_values_only) + xact->dflags |= TRANSACTION_DISPLAYED; + (*handler)(xact); + } last_xact = xact; } @@ -103,8 +122,14 @@ void subtotal_transactions::flush() entry_t * entry = new entry_t; char buf[256]; - // jww (2004-08-10): allow for a format string here - std::strftime(buf, 255, "- %Y/%m/%d", std::gmtime(&finish)); + std::string fmt = "- "; + fmt += format_t::date_format; + + // Make sure the end date is inclusive + if (start != finish) + finish -= 86400; + + std::strftime(buf, 255, fmt.c_str(), std::gmtime(&finish)); entry->payee = buf; entry_temps.push_back(entry); @@ -153,4 +178,37 @@ void subtotal_transactions::operator()(transaction_t * xact) (*i).second += *xact; } +void interval_transactions::operator()(transaction_t * xact) +{ + std::time_t quant = interval.increment(begin); + if (std::difftime(xact->entry->date, quant) > 0) { + if (last_xact) { + start = begin; + finish = quant; + flush(); + } + + if (! interval.seconds) { + struct std::tm * desc = std::gmtime(&xact->entry->date); + if (interval.years) + desc->tm_mon = 0; + desc->tm_mday = 1; + desc->tm_hour = 0; + desc->tm_min = 0; + desc->tm_sec = 0; + quant = std::mktime(desc); + } + + std::time_t temp; + while (std::difftime(xact->entry->date, + temp = interval.increment(quant)) > 0) + quant = temp; + begin = quant; + } + + subtotal_transactions::operator()(xact); + + last_xact = xact; +} + } // namespace ledger @@ -87,19 +87,7 @@ class sort_transactions : public item_handler<transaction_t> delete handler; } - virtual void flush() { - std::stable_sort(transactions.begin(), transactions.end(), - compare_items<transaction_t>(sort_order)); - - for (transactions_deque::iterator i = transactions.begin(); - i != transactions.end(); - i++) - (*handler)(*i); - - transactions.clear(); - - handler->flush(); - } + virtual void flush(); virtual void operator()(transaction_t * xact) { transactions.push_back(xact); @@ -207,6 +195,7 @@ class changed_value_transactions : public item_handler<transaction_t> // This filter requires that calc_transactions be used at some point // later in the chain. + bool changed_values_only; transaction_t * last_xact; item_handler<transaction_t> * handler; @@ -215,8 +204,10 @@ class changed_value_transactions : public item_handler<transaction_t> transactions_deque xact_temps; public: - changed_value_transactions(item_handler<transaction_t> * _handler) - : last_xact(NULL), handler(_handler) {} + changed_value_transactions(item_handler<transaction_t> * _handler, + bool _changed_values_only) + : changed_values_only(_changed_values_only), last_xact(NULL), + handler(_handler) {} virtual ~changed_value_transactions() { flush(); @@ -288,7 +279,8 @@ class interval_transactions : public subtotal_transactions public: interval_transactions(item_handler<transaction_t> * _handler, - std::time_t _begin, const interval_t& _interval) + const interval_t& _interval, + const std::time_t _begin = 0) : subtotal_transactions(_handler), begin(_begin), interval(_interval), last_xact(NULL) {} @@ -297,25 +289,7 @@ class interval_transactions : public subtotal_transactions finish = interval.increment(begin); } - virtual void operator()(transaction_t * xact) { - if (std::difftime(xact->entry->date, interval.increment(begin)) > 0) { - if (last_xact) { - start = begin; - finish = interval.increment(begin); - flush(); - } - - begin = interval.increment(begin); - std::time_t temp; - while (std::difftime(xact->entry->date, - temp = interval.increment(begin)) > 0) - begin = temp; - } - - subtotal_transactions::operator()(xact); - - last_xact = xact; - } + virtual void operator()(transaction_t * xact); }; class related_transactions : public item_handler<transaction_t> |