diff options
-rwxr-xr-x | acprep | 4 | ||||
-rw-r--r-- | binary.cc | 6 | ||||
-rw-r--r-- | config.cc | 11 | ||||
-rw-r--r-- | configure.in | 4 | ||||
-rw-r--r-- | derive.cc | 1 | ||||
-rw-r--r-- | emacs.cc | 25 | ||||
-rw-r--r-- | format.cc | 68 | ||||
-rw-r--r-- | format.h | 2 | ||||
-rw-r--r-- | gnucash.cc | 10 | ||||
-rw-r--r-- | journal.cc | 24 | ||||
-rw-r--r-- | journal.h | 16 | ||||
-rw-r--r-- | ledger.el | 168 | ||||
-rw-r--r-- | ledger.texi | 15 | ||||
-rw-r--r-- | qif.cc | 2 | ||||
-rw-r--r-- | reconcile.cc | 8 | ||||
-rwxr-xr-x | setup.py | 4 | ||||
-rw-r--r-- | textual.cc | 87 | ||||
-rw-r--r-- | valexpr.cc | 21 | ||||
-rw-r--r-- | valexpr.h | 1 | ||||
-rw-r--r-- | walk.cc | 2 | ||||
-rw-r--r-- | xml.cc | 31 | ||||
-rw-r--r-- | xml.h | 2 |
22 files changed, 396 insertions, 116 deletions
@@ -11,10 +11,10 @@ else fi autoconf -INCDIRS="-I/sw/include -I/usr/include/httpd/xml -I/usr/include/python2.3" +INCDIRS="-I/sw/include -I/usr/local/include/boost-1_33 -I/usr/include/httpd/xml -I/usr/include/python2.3" #INCDIRS="$INCDIRS -I/sw/include/libofx" INCDIRS="$INCDIRS -Wno-long-double" -LIBDIRS="-L/sw/lib -L/usr/lib/python2.3/config" +LIBDIRS="-L/sw/lib -L/usr/local/lib -L/usr/lib/python2.3/config" if [ "$1" = "--debug" ]; then ./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" CXXFLAGS="-g" \ @@ -11,7 +11,7 @@ namespace ledger { static unsigned long binary_magic_number = 0xFFEED765; -static unsigned long format_version = 0x00020040; +static unsigned long format_version = 0x00020042; static account_t ** accounts; static account_t ** accounts_next; @@ -188,6 +188,7 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) } else { xact->cost = NULL; } + read_binary_number(data, xact->state); read_binary_number(data, xact->flags); xact->flags |= TRANSACTION_BULK_ALLOC; read_binary_string(data, &xact->note); @@ -218,7 +219,6 @@ inline void read_binary_entry(char *& data, entry_t * entry, { read_binary_entry_base(data, entry, xact_pool); read_binary_number(data, entry->date); - read_binary_number(data, entry->state); read_binary_string(data, &entry->code); read_binary_string(data, &entry->payee); } @@ -548,6 +548,7 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact) } else { write_binary_number<char>(out, 0); } + write_binary_number(out, xact->state); write_binary_number(out, xact->flags); write_binary_string(out, xact->note); } @@ -571,7 +572,6 @@ void write_binary_entry(std::ostream& out, entry_t * entry) { write_binary_entry_base(out, entry); write_binary_number(out, entry->date); - write_binary_number(out, entry->state); write_binary_string(out, entry->code); write_binary_string(out, entry->payee); } @@ -36,10 +36,10 @@ config_t::config_t() "%48|%-.38A %22.108t %22.132T\n"); plot_amount_format = "%D %(St)\n"; plot_total_format = "%D %(ST)\n"; - print_format = "\n%D %X%C%P\n %-34A %12o%n\n%/ %-34A %12o%n\n"; - write_hdr_format = "%D %X%C%P\n"; - write_xact_format = " %-34A %12o%n\n"; - equity_format = "\n%D %X%C%P\n%/ %-34A %12t\n"; + print_format = "\n%D %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"; + write_hdr_format = "%D %Y%C%P\n"; + write_xact_format = " %-34W %12o%n\n"; + equity_format = "\n%D %Y%C%P\n%/ %-34W %12t\n"; #ifndef USE_BOOST_PYTHON prices_format = "%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"; #else @@ -48,6 +48,9 @@ config_t::config_t() #endif pricesdb_format = "P %[%Y/%m/%d %H:%M:%S] %A %t\n"; + predicate = ""; + display_predicate = ""; + head_entries = 0; tail_entries = 0; diff --git a/configure.in b/configure.in index 21dbf1eb..af2a1962 100644 --- a/configure.in +++ b/configure.in @@ -2,8 +2,8 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(ledger, 2.4, johnw@newartisans.com) -AM_INIT_AUTOMAKE(ledger, 2.4) +AC_INIT(ledger, 2.5, johnw@newartisans.com) +AM_INIT_AUTOMAKE(ledger, 2.5) AC_CONFIG_SRCDIR([main.cc]) AC_CONFIG_HEADER([acconf.h]) @@ -36,7 +36,6 @@ entry_t * derive_new_entry(journal_t& journal, if (i == end) { // If no argument were given but the payee, assume the user wants // to see the same transaction as last time. - added->state = matching->state; added->code = matching->code; for (transactions_list::iterator j = matching->transactions.begin(); @@ -15,18 +15,6 @@ void format_emacs_transactions::write_entry(entry_t& entry) out << (((unsigned long)entry.beg_pos) + 1) << " "; - switch (entry.state) { - case entry_t::CLEARED: - out << "t "; - break; - case entry_t::PENDING: - out << "pending "; - break; - case entry_t::UNCLEARED: - out << "nil "; - break; - } - out << "(" << (entry.date / 65536) << " " << (entry.date % 65536) << " 0) "; @@ -61,6 +49,19 @@ void format_emacs_transactions::operator()(transaction_t& xact) out << " (\"" << xact.account->fullname() << "\" \"" << xact.amount << "\""; + + switch (xact.state) { + case transaction_t::CLEARED: + out << " t"; + break; + case transaction_t::PENDING: + out << " pending"; + break; + default: + out << " nil"; + break; + } + if (xact.cost) out << " \"" << *xact.cost << "\""; else if (! xact.note.empty()) @@ -217,8 +217,10 @@ element_t * format_t::parse_elements(const std::string& fmt) case 'E': current->type = element_t::END_POS; break; case 'e': current->type = element_t::END_LINE; break; case 'X': current->type = element_t::CLEARED; break; + case 'Y': current->type = element_t::ENTRY_CLEARED; break; case 'C': current->type = element_t::CODE; break; case 'P': current->type = element_t::PAYEE; break; + case 'W': current->type = element_t::OPT_ACCOUNT; break; case 'a': current->type = element_t::ACCOUNT_NAME; break; case 'A': current->type = element_t::ACCOUNT_FULLNAME; break; case 't': current->type = element_t::AMOUNT; break; @@ -246,10 +248,32 @@ element_t * format_t::parse_elements(const std::string& fmt) return result.release(); } +static bool entry_state(const entry_t * entry, transaction_t::state_t * state) +{ + bool first = true; + bool hetero = false; + + for (transactions_list::const_iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) { + if (first) { + *state = (*i)->state; + first = false; + } + else if (*state != (*i)->state) { + hetero = true; + break; + } + } + + return ! hetero; +} + void format_t::format(std::ostream& out_str, const details_t& details) const { for (const element_t * elem = elements; elem; elem = elem->next) { std::ostringstream out; + std::string name; bool ignore_max_width = false; if (elem->align_left) @@ -408,10 +432,31 @@ void format_t::format(std::ostream& out_str, const details_t& details) const break; case element_t::CLEARED: - if (details.entry && details.entry->state == entry_t::CLEARED) + if (details.xact) { + switch (details.xact->state) { + case transaction_t::CLEARED: out << "* "; - else - out << ""; + break; + case transaction_t::PENDING: + out << "! "; + break; + } + } + break; + + case element_t::ENTRY_CLEARED: + if (details.entry) { + transaction_t::state_t state; + if (entry_state(details.entry, &state)) + switch (state) { + case transaction_t::CLEARED: + out << "* "; + break; + case transaction_t::PENDING: + out << "! "; + break; + } + } break; case element_t::CODE: { @@ -444,10 +489,25 @@ void format_t::format(std::ostream& out_str, const details_t& details) const elem->max_width)); break; + case element_t::OPT_ACCOUNT: + if (details.entry && details.xact) { + transaction_t::state_t state; + if (! entry_state(details.entry, &state)) + switch (details.xact->state) { + case transaction_t::CLEARED: + name = "* "; + break; + case transaction_t::PENDING: + name = "! "; + break; + } + } + // fall through... + case element_t::ACCOUNT_NAME: case element_t::ACCOUNT_FULLNAME: if (details.account) { - std::string name = (elem->type == element_t::ACCOUNT_FULLNAME ? + name += (elem->type == element_t::ACCOUNT_FULLNAME ? details.account->fullname() : partial_account_name(*details.account)); @@ -25,8 +25,10 @@ struct element_t END_LINE, DATE_STRING, CLEARED, + ENTRY_CLEARED, CODE, PAYEE, + OPT_ACCOUNT, ACCOUNT_NAME, ACCOUNT_FULLNAME, AMOUNT, @@ -49,6 +49,8 @@ static unsigned int src_idx; static istream_pos_type beg_pos; static unsigned long beg_line; +static transaction_t::state_t curr_state; + static enum action_t { NO_ACTION, ACCOUNT_NAME, @@ -244,9 +246,9 @@ static void dataHandler(void *userData, const char *s, int len) case XACT_STATE: if (*s == 'y') - curr_entry->state = entry_t::PENDING; + curr_state = transaction_t::PENDING; else - curr_entry->state = entry_t::CLEARED; + curr_state = transaction_t::CLEARED; break; case XACT_VALUE: { @@ -292,9 +294,12 @@ static void dataHandler(void *userData, const char *s, int len) value = curr_quant; } + xact->state = curr_state; xact->amount = value; if (value != curr_value) xact->cost = new amount_t(curr_value); + + curr_state = transaction_t::UNCLEARED; break; } @@ -337,6 +342,7 @@ unsigned int gnucash_parser_t::parse(std::istream& in, curr_entry = NULL; curr_comm = NULL; entry_comm = NULL; + curr_state = transaction_t::UNCLEARED; instreamp = ∈ path = original_file ? *original_file : "<gnucash>"; @@ -19,6 +19,9 @@ bool transaction_t::valid() const if (! entry) return false; + if (state != UNCLEARED && state != CLEARED && state != PENDING) + return false; + bool found = false; for (transactions_list::const_iterator i = entry->transactions.begin(); i != entry->transactions.end(); @@ -198,7 +201,7 @@ bool entry_base_t::finalize() } entry_t::entry_t(const entry_t& e) - : entry_base_t(e), date(e.date), state(e.state), code(e.code), payee(e.payee) + : entry_base_t(e), date(e.date), code(e.code), payee(e.payee) { DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t"); @@ -219,9 +222,6 @@ bool entry_t::valid() const if (! date || ! journal) return false; - if (state != UNCLEARED && state != CLEARED && state != PENDING) - return false; - for (transactions_list::const_iterator i = transactions.begin(); i != transactions.end(); i++) @@ -729,13 +729,22 @@ void export_journal() .add_property("cost", make_getter(&transaction_t::cost, return_internal_reference<1>())) + .def_readwrite("state", &transaction_t::state) .def_readwrite("flags", &transaction_t::flags) .def_readwrite("note", &transaction_t::note) +#if 0 .def_readwrite("data", &transaction_t::data) +#endif .def("valid", &transaction_t::valid) ; + enum_< transaction_t::state_t > ("State") + .value("UNCLEARED", transaction_t::UNCLEARED) + .value("CLEARED", transaction_t::CLEARED) + .value("PENDING", transaction_t::PENDING) + ; + class_< account_t > ("Account", init<optional<account_t *, std::string, std::string> >() @@ -818,7 +827,6 @@ void export_journal() scope in_entry = class_< entry_t, bases<entry_base_t> > ("Entry") .def_readwrite("date", &entry_t::date) - .def_readwrite("state", &entry_t::state) .def_readwrite("code", &entry_t::code) .def_readwrite("payee", &entry_t::payee) @@ -841,12 +849,6 @@ void export_journal() .def("valid", &period_entry_t::valid) ; - enum_< entry_t::state_t > ("State") - .value("UNCLEARED", entry_t::UNCLEARED) - .value("CLEARED", entry_t::CLEARED) - .value("PENDING", entry_t::PENDING) - ; - #define EXC_TRANSLATE(type) \ register_exception_translator<type>(&exc_translate_ ## type); @@ -29,17 +29,20 @@ class account_t; class transaction_t { public: + enum state_t { UNCLEARED, CLEARED, PENDING }; + entry_t * entry; account_t * account; amount_t amount; amount_t * cost; + state_t state; unsigned short flags; std::string note; mutable void * data; transaction_t(account_t * _account = NULL) : entry(NULL), account(_account), cost(NULL), - flags(TRANSACTION_NORMAL), data(NULL) { + state(UNCLEARED), flags(TRANSACTION_NORMAL), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } @@ -48,14 +51,16 @@ class transaction_t unsigned int _flags = TRANSACTION_NORMAL, const std::string& _note = "") : entry(NULL), account(_account), amount(_amount), - cost(NULL), flags(_flags), note(_note), data(NULL) { + cost(NULL), state(UNCLEARED), flags(_flags), + note(_note), data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } transaction_t(const transaction_t& xact) : entry(xact.entry), account(xact.account), amount(xact.amount), cost(xact.cost ? new amount_t(*xact.cost) : NULL), - flags(xact.flags), note(xact.note), data(NULL) { + state(xact.state), flags(xact.flags), note(xact.note), + data(NULL) { DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); } @@ -128,14 +133,11 @@ class entry_base_t class entry_t : public entry_base_t { public: - enum state_t { UNCLEARED, CLEARED, PENDING }; - std::time_t date; - state_t state; std::string code; std::string payee; - entry_t() : date(0), state(UNCLEARED) { + entry_t() : date(0) { DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t"); } entry_t(const entry_t& e); @@ -60,6 +60,11 @@ :type 'file :group 'ledger) +(defcustom ledger-clear-whole-entries nil + "If non-nil, clear whole entries, not individual transactions." + :type 'boolean + :group 'ledger) + (defvar bold 'bold) (defvar ledger-font-lock-keywords '(("^[0-9./]+\\s-+\\(?:([^)]+)\\s-+\\)?\\([^*].+\\)" 1 bold) @@ -159,18 +164,21 @@ Return the difference in the format of a time value." (concat (if insert-year entry-text (substring entry-text 6)) "\n"))) "\n")))) -(defun ledger-delete-current-entry () - (interactive) +(defun ledger-current-entry-bounds () (save-excursion (when (or (looking-at "^[0-9]") (re-search-backward "^[0-9]" nil t)) (let ((beg (point))) (while (not (eolp)) (forward-line)) - (delete-blank-lines) - (delete-region beg (point)))))) + (cons (copy-marker beg) (point-marker)))))) -(defun ledger-toggle-current (&optional style) +(defun ledger-delete-current-entry () + (interactive) + (let ((bounds (ledger-current-entry-bounds))) + (delete-region (car bounds) (cdr bounds)))) + +(defun ledger-toggle-current-entry (&optional style) (interactive) (let (clear) (save-excursion @@ -189,6 +197,118 @@ Return the difference in the format of a time value." (setq clear t)))) clear)) +(defun ledger-toggle-current-transaction (&optional style) + "Toggle the cleared status of the transaction under point. +Optional argument STYLE may be `pending' or `cleared', depending +on which type of status the caller wishes to indicate (default is +`cleared'). +This function is rather complicated because it must preserve both +the overall formatting of the ledger entry, as well as ensuring +that the most minimal display format is used. This could be +achieved more certainly by passing the entry to ledger for +formatting, but doing so causes inline math expressions to be +dropped." + (interactive) + (let ((bounds (ledger-current-entry-bounds)) + clear cleared) + ;; Uncompact the entry, to make it easier to toggle the + ;; transaction + (save-excursion + (goto-char (car bounds)) + (skip-chars-forward "0-9./ \t") + (setq cleared (and (member (char-after) '(?\* ?\!)) + (char-after))) + (when cleared + (let ((here (point))) + (skip-chars-forward "*! ") + (let ((width (- (point) here))) + (when (> width 0) + (delete-region here (point)) + (if (search-forward " " (line-end-position) t) + (insert (make-string width ? )))))) + (forward-line) + (while (looking-at "[ \t]") + (skip-chars-forward " \t") + (assert (not (looking-at "[!*]"))) + (insert cleared " ") + (if (search-forward " " (line-end-position) t) + (delete-char 2)) + (forward-line)))) + ;; Toggle the individual transaction + (save-excursion + (goto-char (line-beginning-position)) + (when (looking-at "[ \t]") + (skip-chars-forward " \t") + (let ((here (point)) + (cleared (member (char-after) '(?\* ?\!)))) + (skip-chars-forward "*! ") + (let ((width (- (point) here))) + (when (> width 0) + (delete-region here (point)) + (save-excursion + (if (search-forward " " (line-end-position) t) + (insert (make-string width ? )))))) + (let (inserted) + (if cleared + (if (and style (eq style 'cleared)) + (progn + (insert "* ") + (setq inserted t))) + (if (and style (eq style 'pending)) + (progn + (insert "! ") + (setq inserted t)) + (progn + (insert "* ") + (setq inserted t)))) + (if (and inserted + (search-forward " " (line-end-position) t)) + (delete-char 2)) + (setq clear inserted))))) + ;; Clean up the entry so that it displays minimally + (save-excursion + (goto-char (car bounds)) + (forward-line) + (let ((first t) + (state ? ) + (hetero nil)) + (while (and (not hetero) (looking-at "[ \t]")) + (skip-chars-forward " \t") + (let ((cleared (if (member (char-after) '(?\* ?\!)) + (char-after) + ? ))) + (if first + (setq state cleared + first nil) + (if (/= state cleared) + (setq hetero t)))) + (forward-line)) + (when (and (not hetero) (/= state ? )) + (goto-char (car bounds)) + (forward-line) + (while (looking-at "[ \t]") + (skip-chars-forward " \t") + (let ((here (point))) + (skip-chars-forward "*! ") + (let ((width (- (point) here))) + (when (> width 0) + (delete-region here (point)) + (if (search-forward " " (line-end-position) t) + (insert (make-string width ? )))))) + (forward-line)) + (goto-char (car bounds)) + (skip-chars-forward "0-9./ \t") + (insert state " ") + (if (search-forward " " (line-end-position) t) + (delete-char 2))))) + clear)) + +(defun ledger-toggle-current (&optional style) + (interactive) + (if ledger-clear-whole-entries + (ledger-toggle-current-entry style) + (ledger-toggle-current-transaction style))) + (defvar ledger-mode-abbrev-table) (define-derived-mode ledger-mode text-mode "Ledger" @@ -365,20 +485,33 @@ Return the difference in the format of a time value." (error (buffer-string))) (read (current-buffer)))))))) (dolist (item items) - (dolist (xact (nthcdr 6 item)) + (let ((index 1)) + (dolist (xact (nthcdr 5 item)) (let ((beg (point)) - (where (with-current-buffer buf - (cons (nth 0 item) - (copy-marker (nth 1 item)))))) + (where + (with-current-buffer buf + (cons + (nth 0 item) + (if ledger-clear-whole-entries + (copy-marker (nth 1 item)) + (save-excursion + (goto-char (nth 1 item)) + (let ((i 0)) + (while (< i index) + (re-search-forward + account (cdr (ledger-current-entry-bounds))) + (setq i (1+ i)))) + (point-marker))))))) (insert (format "%s %-30s %-25s %15s\n" - (format-time-string "%m/%d" (nth 3 item)) - (nth 5 item) (nth 0 xact) (nth 1 xact))) - (if (nth 2 item) + (format-time-string "%m/%d" (nth 2 item)) + (nth 4 item) (nth 0 xact) (nth 1 xact))) + (if (nth 2 xact) (set-text-properties beg (1- (point)) (list 'face 'bold 'where where)) (set-text-properties beg (1- (point)) - (list 'where where)))))) + (list 'where where)))) + (setq index (1+ index))))) (goto-char (point-min)) (set-buffer-modified-p nil) (toggle-read-only t))) @@ -458,6 +591,15 @@ Return the difference in the format of a time value." ledger-binary-path nil buf nil "-f" "-") args))))) +(defun ledger-run-ledger-and-delete (buffer &rest args) + "run ledger with supplied arguments" + (let ((buf (current-buffer))) + (with-current-buffer buffer + (apply #'call-process-region + (append (list (point-min) (point-max) + ledger-binary-path t buf nil "-f" "-") + args))))) + (defun ledger-set-year (newyear) "Set ledger's idea of the current year to the prefix argument." (interactive "p") diff --git a/ledger.texi b/ledger.texi index b7aa084a..bf756c1a 100644 --- a/ledger.texi +++ b/ledger.texi @@ -1267,13 +1267,13 @@ options: @item --equity-format @command{equity} report. Default: @smallexample -\n%D %X%C%P\n %-34A %12o%n\n%/ %-34A %12o%n\n +\n%D %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n @end smallexample @item --prices-format @command{prices} report. Default: @smallexample -\n%D %X%C%P\n%/ %-34A %12t\n +\n%D %Y%C%P\n%/ %-34W %12t\n @end smallexample @item --wide-register-format @@ -1987,6 +1987,10 @@ output. If a transaction has been cleared, this inserts @samp{*} followed by a space; otherwise nothing is inserted. +@item Y +This is the same as @samp{%X}, except that it only displays a state +character if all of the member transactions have the same state. + @item C Inserts the checking number for an entry, in parentheses, followed by a space; if none was specified, nothing is inserted. @@ -2002,6 +2006,13 @@ has not been printed yet, otherwise it just prints the account's name. @item A Inserts the full name of an account. +@item W +This is the same as @samp{%A}, except that it first displays the +transaction's state @emph{if the entry's transaction states are not +all the same}, followed by the full account name. This is offered as +a printing optimization, so that combined with @samp{%Y}, only the +minimum amount of state detail is printed. + @item o Inserts the ``optimized'' form of a transaction's amount. This is used by the print report. In some cases, this inserts nothing; in @@ -138,7 +138,7 @@ unsigned int qif_parser_t::parse(std::istream& in, c = in.peek(); if (c == '*' || c == 'X') { in.get(c); - entry->state = entry_t::CLEARED; + xact->state = transaction_t::CLEARED; } break; diff --git a/reconcile.cc b/reconcile.cc index 0ba571b7..07e2125d 100644 --- a/reconcile.cc +++ b/reconcile.cc @@ -45,12 +45,12 @@ void reconcile_transactions::flush() x != xacts.end(); x++) { if (! cutoff || std::difftime((*x)->entry->date, cutoff) < 0) { - switch ((*x)->entry->state) { - case entry_t::CLEARED: + switch ((*x)->state) { + case transaction_t::CLEARED: cleared_balance += (*x)->amount; break; - case entry_t::UNCLEARED: - case entry_t::PENDING: + case transaction_t::UNCLEARED: + case transaction_t::PENDING: pending_balance += (*x)->amount; *last_ptr = *x; last_ptr = xact_next_ptr(*x); @@ -19,11 +19,11 @@ if os.environ.has_key ("HAVE_LIBOFX") and\ libs.extend (["ofx"]) setup(name = "Ledger", - version = "2.0b", + version = "2.5", description = "Ledger Accounting Tool", author = "John Wiegley", author_email = "johnw@newartisans.com", - url = "http://www.newartisans.com/johnw/", + url = "http://www.newartisans.com/ledger.html", ext_modules = [ Extension("ledger", ["pyledger.cc"], define_macros = [('PYTHON_MODULE', 1)], @@ -174,7 +174,6 @@ void parse_amount(const char * text, amount_t& amt, unsigned short flags, transaction_t * parse_transaction(char * line, account_t * account) { // The account will be determined later... - std::auto_ptr<transaction_t> xact(new transaction_t(NULL)); // The call to `next_element' will skip past the account name, and @@ -182,45 +181,48 @@ transaction_t * parse_transaction(char * line, account_t * account) // where the amount is, we can strip off any transaction note, and // parse it. + char * amount = NULL; + char * price = NULL; + bool per_unit = true; + char * p = skip_ws(line); - if (char * cost_str = next_element(p, true)) { - cost_str = skip_ws(cost_str); - bool has_amount = *cost_str; + switch (*p) { + case '*': + xact->state = transaction_t::CLEARED; + p = skip_ws(++p); + break; + case '!': + xact->state = transaction_t::PENDING; + p = skip_ws(++p); + break; + } - if (char * note_str = std::strchr(cost_str, ';')) { - if (cost_str == note_str) - has_amount = false; + if (amount = next_element(p, true)) { + amount = skip_ws(amount); + if (! *amount) + amount = NULL; + else { + if (char * note_str = std::strchr(amount, ';')) { + if (amount == note_str) + amount = NULL; *note_str++ = '\0'; xact->note = skip_ws(note_str); } - if (has_amount) { - bool per_unit = true; - char * price_str = std::strchr(cost_str, '@'); - if (price_str) { - if (price_str == cost_str) + if (amount) { + price = std::strchr(amount, '@'); + if (price) { + if (price == amount) throw parse_error(path, linenum, "Cost specified without amount"); - *price_str++ = '\0'; - if (*price_str == '@') { + *price++ = '\0'; + if (*price == '@') { per_unit = false; - price_str++; + price++; } + price = skip_ws(price); } - - parse_amount(skip_ws(cost_str), xact->amount, AMOUNT_PARSE_NO_REDUCE, - *xact); - if (price_str) { - xact->cost = new amount_t; - parse_amount(skip_ws(price_str), *xact->cost, AMOUNT_PARSE_NO_MIGRATE, - *xact); } - - if (price_str && per_unit) { - *xact->cost *= xact->amount; - *xact->cost = xact->cost->round(xact->cost->commodity().precision); - } - xact->amount.reduce(); } } @@ -247,6 +249,22 @@ transaction_t * parse_transaction(char * line, account_t * account) if (! xact->account) xact->account = account->find_account(p); + // If an amount (and optional price) were seen, parse them now + if (amount) { + parse_amount(amount, xact->amount, AMOUNT_PARSE_NO_REDUCE, *xact); + + if (price) { + xact->cost = new amount_t; + parse_amount(price, *xact->cost, AMOUNT_PARSE_NO_MIGRATE, *xact); + + if (per_unit) { + *xact->cost *= xact->amount; + *xact->cost = xact->cost->round(xact->cost->commodity().precision); + } + } + xact->amount.reduce(); + } + return xact.release(); } @@ -304,14 +322,15 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, TIMER_START(entry_details); + transaction_t::state_t state = transaction_t::UNCLEARED; if (next) { switch (*next) { case '*': - curr->state = entry_t::CLEARED; + state = transaction_t::CLEARED; next = skip_ws(++next); break; case '!': - curr->state = entry_t::PENDING; + state = transaction_t::PENDING; next = skip_ws(++next); break; } @@ -350,8 +369,12 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, break; } - if (transaction_t * xact = parse_transaction(line, master)) + if (transaction_t * xact = parse_transaction(line, master)) { + if (state != transaction_t::UNCLEARED && + xact->state == transaction_t::UNCLEARED) + xact->state = state; curr->add_transaction(xact); + } if (in.eof()) break; @@ -417,7 +440,6 @@ static void clock_out_from_timelog(const std::time_t when, { std::auto_ptr<entry_t> curr(new entry_t); curr->date = when; - curr->state = entry_t::CLEARED; curr->code = ""; curr->payee = last_desc; @@ -429,6 +451,7 @@ static void clock_out_from_timelog(const std::time_t when, transaction_t * xact = new transaction_t(last_account, amt, TRANSACTION_VIRTUAL); + xact->state = transaction_t::CLEARED; curr->add_transaction(xact); if (! journal->add_entry(curr.get())) @@ -114,8 +114,8 @@ void value_expr_t::compute(value_t& result, const details_t& details) const break; case CLEARED: - if (details.entry) - result = details.entry->state == entry_t::CLEARED; + if (details.xact) + result = details.xact->state == transaction_t::CLEARED; else result = false; break; @@ -273,6 +273,14 @@ void value_expr_t::compute(value_t& result, const details_t& details) const result = false; break; + case F_COMMODITY_MASK: + assert(mask); + if (details.xact) + result = mask->match(details.xact->amount.commodity().symbol); + else + result = false; + break; + case F_VALUE: { assert(left); left->compute(result, details); @@ -512,12 +520,14 @@ value_expr_t * parse_value_term(std::istream& in) // Other case 'c': + case 'C': case 'p': case 'w': case 'W': case 'e': case '/': { bool code_mask = c == 'c'; + bool commodity_mask = c == 'C'; bool payee_mask = c == 'p'; bool note_mask = c == 'e'; bool short_account_mask = c == 'w'; @@ -550,6 +560,8 @@ value_expr_t * parse_value_term(std::istream& in) kind = value_expr_t::F_SHORT_ACCOUNT_MASK; else if (code_mask) kind = value_expr_t::F_CODE_MASK; + else if (commodity_mask) + kind = value_expr_t::F_COMMODITY_MASK; else if (payee_mask) kind = value_expr_t::F_PAYEE_MASK; else if (note_mask) @@ -888,6 +900,11 @@ void dump_value_expr(std::ostream& out, const value_expr_t * node) out << "M_SACCT(" << node->mask->pattern << ')'; break; + case value_expr_t::F_COMMODITY_MASK: + assert(node->mask); + out << "M_COMM(" << node->mask->pattern << ')'; + break; + case value_expr_t::F_VALUE: out << "VALUE("; dump_value_expr(out, node->left); @@ -74,6 +74,7 @@ struct value_expr_t F_NOTE_MASK, F_ACCOUNT_MASK, F_SHORT_ACCOUNT_MASK, + F_COMMODITY_MASK, F_INTERP_FUNC, // Binary operators @@ -494,13 +494,13 @@ void set_comm_as_payee::operator()(transaction_t& xact) entry_temps.push_back(*xact.entry); entry_t& entry = entry_temps.back(); entry.date = xact.entry->date; - entry.state = xact.entry->state; entry.code = xact.entry->code; entry.payee = xact.amount.commodity().symbol; xact_temps.push_back(xact); transaction_t& temp = xact_temps.back(); temp.entry = &entry; + temp.state = xact.state; temp.flags |= TRANSACTION_BULK_ALLOC; entry.add_transaction(&temp); @@ -27,6 +27,8 @@ static entry_t * curr_entry; static commodity_t * curr_comm; static std::string comm_flags; +static transaction_t::state_t curr_state; + static std::string data; static bool ignore; static std::string have_error; @@ -39,10 +41,13 @@ static void startElement(void *userData, const char *name, const char **attrs) if (std::strcmp(name, "entry") == 0) { assert(! curr_entry); curr_entry = new entry_t; + curr_state = transaction_t::UNCLEARED; } else if (std::strcmp(name, "transaction") == 0) { assert(curr_entry); curr_entry->add_transaction(new transaction_t); + if (curr_state != transaction_t::UNCLEARED) + curr_entry->transactions.back()->state = curr_state; } else if (std::strcmp(name, "commodity") == 0) { if (std::string(attrs[0]) == "flags") @@ -80,14 +85,14 @@ static void endElement(void *userData, const char *name) else if (std::strcmp(name, "en:date") == 0) { quick_parse_date(data.c_str(), &curr_entry->date); } + else if (std::strcmp(name, "en:code") == 0) { + curr_entry->code = data; + } else if (std::strcmp(name, "en:cleared") == 0) { - curr_entry->state = entry_t::CLEARED; + curr_state = transaction_t::CLEARED; } else if (std::strcmp(name, "en:pending") == 0) { - curr_entry->state = entry_t::PENDING; - } - else if (std::strcmp(name, "en:code") == 0) { - curr_entry->code = data; + curr_state = transaction_t::PENDING; } else if (std::strcmp(name, "en:payee") == 0) { curr_entry->payee = data; @@ -95,6 +100,12 @@ static void endElement(void *userData, const char *name) else if (std::strcmp(name, "tr:account") == 0) { curr_entry->transactions.back()->account = curr_journal->find_account(data); } + else if (std::strcmp(name, "tr:cleared") == 0) { + curr_entry->transactions.back()->state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "tr:pending") == 0) { + curr_entry->transactions.back()->state = transaction_t::PENDING; + } else if (std::strcmp(name, "tr:virtual") == 0) { curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; } @@ -325,11 +336,6 @@ void format_xml_entries::format_last_entry() output_stream << " <entry>\n" << " <en:date>" << buf << "</en:date>\n"; - if (last_entry->state == entry_t::CLEARED) - output_stream << " <en:cleared/>\n"; - else if (last_entry->state == entry_t::PENDING) - output_stream << " <en:pending/>\n"; - if (! last_entry->code.empty()) { output_stream << " <en:code>"; output_xml_string(output_stream, last_entry->code); @@ -355,6 +361,11 @@ void format_xml_entries::format_last_entry() output_stream << " <transaction>\n"; + if ((*i)->state == transaction_t::CLEARED) + output_stream << " <tr:cleared/>\n"; + else if ((*i)->state == transaction_t::PENDING) + output_stream << " <tr:pending/>\n"; + if ((*i)->flags & TRANSACTION_VIRTUAL) output_stream << " <tr:virtual/>\n"; if ((*i)->flags & TRANSACTION_AUTO) @@ -25,7 +25,7 @@ class format_xml_entries : public format_entries const bool _show_totals = false) : format_entries(output_stream, ""), show_totals(_show_totals) { output_stream << "<?xml version=\"1.0\"?>\n" - << "<ledger version=\"2.0\">\n"; + << "<ledger version=\"2.5\">\n"; } virtual void flush() { |