summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xacprep4
-rw-r--r--binary.cc6
-rw-r--r--config.cc11
-rw-r--r--configure.in4
-rw-r--r--derive.cc1
-rw-r--r--emacs.cc25
-rw-r--r--format.cc68
-rw-r--r--format.h2
-rw-r--r--gnucash.cc10
-rw-r--r--journal.cc24
-rw-r--r--journal.h16
-rw-r--r--ledger.el168
-rw-r--r--ledger.texi15
-rw-r--r--qif.cc2
-rw-r--r--reconcile.cc8
-rwxr-xr-xsetup.py4
-rw-r--r--textual.cc87
-rw-r--r--valexpr.cc21
-rw-r--r--valexpr.h1
-rw-r--r--walk.cc2
-rw-r--r--xml.cc31
-rw-r--r--xml.h2
22 files changed, 396 insertions, 116 deletions
diff --git a/acprep b/acprep
index aa3741c1..c6e0c65d 100755
--- a/acprep
+++ b/acprep
@@ -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" \
diff --git a/binary.cc b/binary.cc
index e9ac3deb..2a573f8f 100644
--- a/binary.cc
+++ b/binary.cc
@@ -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);
}
diff --git a/config.cc b/config.cc
index 43426480..9bebfa7c 100644
--- a/config.cc
+++ b/config.cc
@@ -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])
diff --git a/derive.cc b/derive.cc
index eb0c2660..5141c18e 100644
--- a/derive.cc
+++ b/derive.cc
@@ -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();
diff --git a/emacs.cc b/emacs.cc
index 76d10fa6..d4e9c1f5 100644
--- a/emacs.cc
+++ b/emacs.cc
@@ -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())
diff --git a/format.cc b/format.cc
index e3a24225..f31289e4 100644
--- a/format.cc
+++ b/format.cc
@@ -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));
diff --git a/format.h b/format.h
index 09493024..d2975141 100644
--- a/format.h
+++ b/format.h
@@ -25,8 +25,10 @@ struct element_t
END_LINE,
DATE_STRING,
CLEARED,
+ ENTRY_CLEARED,
CODE,
PAYEE,
+ OPT_ACCOUNT,
ACCOUNT_NAME,
ACCOUNT_FULLNAME,
AMOUNT,
diff --git a/gnucash.cc b/gnucash.cc
index 5f883059..45c0e0dc 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -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 = &in;
path = original_file ? *original_file : "<gnucash>";
diff --git a/journal.cc b/journal.cc
index 298755d2..8310b431 100644
--- a/journal.cc
+++ b/journal.cc
@@ -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);
diff --git a/journal.h b/journal.h
index ffba58f0..b34accc3 100644
--- a/journal.h
+++ b/journal.h
@@ -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);
diff --git a/ledger.el b/ledger.el
index ea4fe283..492ef306 100644
--- a/ledger.el
+++ b/ledger.el
@@ -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
diff --git a/qif.cc b/qif.cc
index 39df84a5..cb72de9a 100644
--- a/qif.cc
+++ b/qif.cc
@@ -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);
diff --git a/setup.py b/setup.py
index 5b89e7e5..79ed0d5f 100755
--- a/setup.py
+++ b/setup.py
@@ -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)],
diff --git a/textual.cc b/textual.cc
index e15a95e8..9ff39435 100644
--- a/textual.cc
+++ b/textual.cc
@@ -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()))
diff --git a/valexpr.cc b/valexpr.cc
index 9091672f..f2b369ae 100644
--- a/valexpr.cc
+++ b/valexpr.cc
@@ -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);
diff --git a/valexpr.h b/valexpr.h
index de4a7a19..bae9bbab 100644
--- a/valexpr.h
+++ b/valexpr.h
@@ -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
diff --git a/walk.cc b/walk.cc
index d80a0397..fe7d6076 100644
--- a/walk.cc
+++ b/walk.cc
@@ -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);
diff --git a/xml.cc b/xml.cc
index 63043fbe..6dea19cf 100644
--- a/xml.cc
+++ b/xml.cc
@@ -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)
diff --git a/xml.h b/xml.h
index a4a4ebe8..fc56aa0a 100644
--- a/xml.h
+++ b/xml.h
@@ -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() {