moved entry hooking mechanism to journal_t; further improvements to "entry"

This commit is contained in:
John Wiegley 2004-09-23 21:08:42 -04:00
parent 02580c2efb
commit c57bfb72c3
9 changed files with 191 additions and 147 deletions

View file

@ -2,10 +2,10 @@
namespace ledger {
void automated_transaction_t::extend_entry(entry_t * entry)
void automated_transaction_t::extend_entry(entry_t& entry)
{
transactions_deque initial_xacts(entry->transactions.begin(),
entry->transactions.end());
transactions_deque initial_xacts(entry.transactions.begin(),
entry.transactions.end());
for (transactions_deque::iterator i = initial_xacts.begin();
i != initial_xacts.end();
@ -23,13 +23,13 @@ void automated_transaction_t::extend_entry(entry_t * entry)
transaction_t * xact
= new transaction_t((*t)->account, amt,
(*t)->flags | TRANSACTION_AUTO);
entry->add_transaction(xact);
entry.add_transaction(xact);
}
}
automated_transactions_t * current_auto_xacts = NULL;
bool handle_auto_xacts(entry_t * entry)
bool handle_auto_xacts(entry_t& entry)
{
if (current_auto_xacts &&
! current_auto_xacts->automated_transactions.empty())
@ -39,3 +39,17 @@ bool handle_auto_xacts(entry_t * entry)
}
} // namespace ledger
#ifdef USE_BOOST_PYTHON
#include <boost/python.hpp>
#include <Python.h>
using namespace boost::python;
using namespace ledger;
void export_autoxact() {
def("handle_auto_xacts", handle_auto_xacts);
}
#endif // USE_BOOST_PYTHON

View file

@ -34,7 +34,7 @@ public:
delete *i;
}
void extend_entry(entry_t * entry);
void extend_entry(entry_t& entry);
};
@ -63,7 +63,7 @@ public:
delete *i;
}
void extend_entry(entry_t * entry) {
void extend_entry(entry_t& entry) {
for (automated_transactions_deque::iterator i
= automated_transactions.begin();
i != automated_transactions.end();
@ -90,7 +90,7 @@ public:
extern automated_transactions_t * current_auto_xacts;
bool handle_auto_xacts(entry_t * entry);
bool handle_auto_xacts(entry_t& entry);
} // namespace ledger

View file

@ -120,6 +120,93 @@ bool entry_t::remove_transaction(transaction_t * xact)
return true;
}
bool finalize_entry(entry_t& entry)
{
// Scan through and compute the total balance for the entry. This
// is used for auto-calculating the value of entries with no cost,
// and the per-unit price of unpriced commodities.
value_t balance;
bool no_amounts = true;
for (transactions_list::const_iterator x = entry.transactions.begin();
x != entry.transactions.end();
x++)
if (! ((*x)->flags & TRANSACTION_VIRTUAL) ||
((*x)->flags & TRANSACTION_BALANCE)) {
amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount;
if (*p) {
if (no_amounts) {
balance = *p;
no_amounts = false;
} else {
balance += *p;
}
}
}
// If it's a null entry, then let the user have their fun
if (no_amounts)
return true;
// If one transaction of a two-line transaction is of a different
// commodity than the others, and it has no per-unit price,
// determine its price by dividing the unit count into the value of
// the balance. This is done for the last eligible commodity.
if (balance.type == value_t::BALANCE &&
((balance_t *) balance.data)->amounts.size() == 2)
for (transactions_list::const_iterator x = entry.transactions.begin();
x != entry.transactions.end();
x++) {
if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL))
continue;
for (amounts_map::const_iterator i
= ((balance_t *) balance.data)->amounts.begin();
i != ((balance_t *) balance.data)->amounts.end();
i++)
if ((*i).second.commodity() != (*x)->amount.commodity()) {
assert((*x)->amount);
balance -= (*x)->amount;
assert(! (*x)->cost);
(*x)->cost = new amount_t(- (*i).second);
balance += *(*x)->cost;
break;
}
break;
}
// Walk through each of the transactions, fixing up any that we
// can, and performing any on-the-fly calculations.
bool empty_allowed = true;
for (transactions_list::const_iterator x = entry.transactions.begin();
x != entry.transactions.end();
x++) {
if ((*x)->amount || ((*x)->flags & TRANSACTION_VIRTUAL))
continue;
if (! empty_allowed || ! balance || balance.type != value_t::AMOUNT)
return false;
empty_allowed = false;
// If one transaction gives no value at all -- and all the
// rest are of the same commodity -- then its value is the
// inverse of the computed value of the others.
(*x)->amount = *((amount_t *) balance.data);
(*x)->amount.negate();
balance = 0U;
}
return ! balance;
}
bool entry_t::valid() const
{
if (! date || date == -1)
@ -296,6 +383,11 @@ journal_t::~journal_t()
bool journal_t::add_entry(entry_t * entry)
{
if (! run_hooks(entry_finalize_hooks, *entry)) {
delete entry;
return false;
}
entries.push_back(entry);
for (transactions_list::const_iterator i = entry->transactions.begin();
@ -439,6 +531,9 @@ entry_t * journal_t::derive_entry(strings_list::iterator i,
}
done:
if (! run_hooks(entry_finalize_hooks, *added))
return NULL;
return added.release();
}
@ -590,6 +685,27 @@ account_t * py_find_account_2(journal_t& journal, const std::string& name,
return journal.find_account(name, auto_create);
}
#if 0
void py_add_entry_finalize_hook(journal_t& journal, object x)
{
add_hook(journal.entry_finalize_hooks,
extract<journal_t::entry_finalize_hook_t>(x));
}
void py_remove_entry_finalize_hook(journal_t& journal, object x)
{
remove_hook(journal.entry_finalize_hooks,
extract<journal_t::entry_finalize_hook_t>(x));
}
void py_run_entry_finalize_hooks(journal_t& journal, entry_t& entry)
{
run_hooks(journal.entry_finalize_hooks, entry);
}
#endif
#define EXC_TRANSLATOR(type) \
void exc_translate_ ## type(const type& err) { \
PyErr_SetString(PyExc_RuntimeError, err.what()); \
@ -677,6 +793,11 @@ void export_journal()
.def("add_entry", &journal_t::add_entry)
.def("remove_entry", &journal_t::remove_entry)
#if 0
.def("add_entry_finalize_hook", py_add_entry_finalize_hook)
.def("remove_entry_finalize_hook", py_remove_entry_finalize_hook)
.def("run_entry_finalize_hooks", py_run_entry_finalize_hooks)
#endif
.def("derive_entry", &journal_t::derive_entry,
return_value_policy<manage_new_object>())

View file

@ -100,11 +100,10 @@
(if (time-less-p moment date)
(throw 'found t)))))))
(defun ledger-add-entry (entry)
(defun ledger-add-entry (entry-text)
(interactive
(list (read-string "Entry: " (format-time-string "%Y/%m/%d "))))
(let* ((args (mapcar 'shell-quote-argument (split-string entry)))
(date (car args))
(list (read-string "Entry: " (format-time-string "%Y/%m/"))))
(let* ((date (car (split-string entry-text)))
(insert-year t) exit-code)
(if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date)
(setq date (encode-time 0 0 0 (string-to-int (match-string 3 date))
@ -117,7 +116,7 @@
(save-excursion
(insert
(with-temp-buffer
(setq exit-code (apply 'ledger-run-ledger "entry" args))
(setq exit-code (ledger-run-ledger "entry" entry-text))
(if (= 0 exit-code)
(progn
(goto-char (point-min))
@ -125,8 +124,8 @@
(if insert-year
(buffer-string)
(buffer-substring 5 (point-max))))
(concat (if insert-year entry
(substring entry 5)) "\n"))) "\n"))))
(concat (if insert-year entry-text
(substring entry-text 5)) "\n"))) "\n"))))
(defun ledger-expand-entry ()
(interactive)
@ -303,8 +302,11 @@
(defun ledger-run-ledger (&rest args)
"run ledger with supplied arguments"
(apply 'call-process ledger-binary-path nil t nil
(append (list "-f" ledger-data-file) args)))
(let ((command (mapconcat 'identity
(append (list ledger-binary-path
"-f" ledger-data-file) args) " ")))
(insert (shell-command-to-string command)))
0)
(provide 'ledger)

View file

@ -116,6 +116,8 @@ class entry_t
bool valid() const;
};
bool finalize_entry(entry_t& entry);
typedef std::map<const std::string, account_t *> accounts_map;
typedef std::pair<const std::string, account_t *> accounts_pair;
@ -181,6 +183,30 @@ class account_t
std::ostream& operator<<(std::ostream& out, const account_t& account);
template <typename T>
void add_hook(std::list<T>& list, T func, const bool prepend = false) {
if (prepend)
list.push_front(func);
else
list.push_back(func);
}
template <typename T>
void remove_hook(std::list<T>& list, T func) {
list.remove(func);
}
template <typename T, typename Data>
bool run_hooks(std::list<T>& list, Data& entry) {
for (typename std::list<T>::const_iterator i = list.begin();
i != list.end();
i++)
if (! (*i)(entry))
return false;
return true;
}
typedef std::list<entry_t *> entries_list;
typedef std::list<std::string> strings_list;
@ -195,9 +221,14 @@ class journal_t
mutable accounts_map accounts_cache;
typedef bool (*entry_finalize_hook_t)(entry_t& entry);
std::list<entry_finalize_hook_t> entry_finalize_hooks;
journal_t() {
master = new account_t(NULL, "");
item_pool = item_pool_end = NULL;
add_hook(entry_finalize_hooks, finalize_entry);
}
~journal_t();

View file

@ -164,6 +164,8 @@ int parse_and_report(int argc, char * argv[], char * envp[])
{
std::auto_ptr<journal_t> journal(new journal_t);
add_hook(journal->entry_finalize_hooks, handle_auto_xacts);
// Parse command-line arguments, and those set in the environment
TIMER_START(process_opts);
@ -224,7 +226,6 @@ int parse_and_report(int argc, char * argv[], char * envp[])
#endif
std::auto_ptr<qif_parser_t> qif_parser(new qif_parser_t);
std::auto_ptr<textual_parser_t> text_parser(new textual_parser_t);
text_parser->add_finalize_hook(handle_auto_xacts);
register_parser(bin_parser.get());
#ifdef READ_GNUCASH

View file

@ -28,6 +28,7 @@ void export_walk();
void export_format();
void export_valexpr();
void export_datetime();
void export_autoxact();
void initialize_ledger_for_python()
{
@ -48,6 +49,7 @@ void initialize_ledger_for_python()
export_format();
export_valexpr();
export_datetime();
export_autoxact();
module_initialized = true;
}

View file

@ -145,93 +145,6 @@ void parse_automated_transactions(std::istream& in,
add_automated_transaction(new automated_transaction_t(line + 1, xacts));
}
bool finalize_entry(entry_t * entry)
{
// Scan through and compute the total balance for the entry. This
// is used for auto-calculating the value of entries with no cost,
// and the per-unit price of unpriced commodities.
value_t balance;
bool no_amounts = true;
for (transactions_list::const_iterator x = entry->transactions.begin();
x != entry->transactions.end();
x++)
if (! ((*x)->flags & TRANSACTION_VIRTUAL) ||
((*x)->flags & TRANSACTION_BALANCE)) {
amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount;
if (*p) {
if (no_amounts) {
balance = *p;
no_amounts = false;
} else {
balance += *p;
}
}
}
// If it's a null entry, then let the user have their fun
if (no_amounts)
return true;
// If one transaction of a two-line transaction is of a different
// commodity than the others, and it has no per-unit price,
// determine its price by dividing the unit count into the value of
// the balance. This is done for the last eligible commodity.
if (balance.type == value_t::BALANCE &&
((balance_t *) balance.data)->amounts.size() == 2)
for (transactions_list::const_iterator x = entry->transactions.begin();
x != entry->transactions.end();
x++) {
if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL))
continue;
for (amounts_map::const_iterator i
= ((balance_t *) balance.data)->amounts.begin();
i != ((balance_t *) balance.data)->amounts.end();
i++)
if ((*i).second.commodity() != (*x)->amount.commodity()) {
assert((*x)->amount);
balance -= (*x)->amount;
assert(! (*x)->cost);
(*x)->cost = new amount_t(- (*i).second);
balance += *(*x)->cost;
break;
}
break;
}
// Walk through each of the transactions, fixing up any that we
// can, and performing any on-the-fly calculations.
bool empty_allowed = true;
for (transactions_list::const_iterator x = entry->transactions.begin();
x != entry->transactions.end();
x++) {
if ((*x)->amount || ((*x)->flags & TRANSACTION_VIRTUAL))
continue;
if (! empty_allowed || ! balance || balance.type != value_t::AMOUNT)
return false;
empty_allowed = false;
// If one transaction gives no value at all -- and all the
// rest are of the same commodity -- then its value is the
// inverse of the computed value of the others.
(*x)->amount = *((amount_t *) balance.data);
(*x)->amount.negate();
balance = 0U;
}
return ! balance;
}
namespace {
TIMER_DEF(entry_finish, "finalizing entry");
TIMER_DEF(entry_xacts, "parsing transactions");
@ -294,17 +207,6 @@ entry_t * parse_entry(std::istream& in, account_t * master,
TIMER_STOP(entry_xacts);
// If there were no transactions, throw away the entry
TIMER_START(entry_finish);
if (curr->transactions.empty() ||
! parser.run_finalize_hooks(curr.get())) {
return NULL; // ~auto_ptr will delete curr
}
TIMER_STOP(entry_finish);
return curr.release();
}
@ -423,10 +325,10 @@ unsigned int textual_parser_t::parse(std::istream& in,
= new transaction_t(last_account, amt, TRANSACTION_VIRTUAL);
curr->add_transaction(xact);
if (! run_finalize_hooks(curr.get()) ||
! journal->add_entry(curr.release()))
if (! journal->add_entry(curr.release()))
throw parse_error(path, linenum,
"Failed to record 'out' timelog entry");
count++;
} else {
throw parse_error(path, linenum, "Cannot parse timelog entry date");

View file

@ -5,42 +5,13 @@
namespace ledger {
bool finalize_entry(entry_t * entry);
class textual_parser_t : public parser_t
{
public:
typedef bool (*finalize_hook_t)(entry_t * entry);
std::list<finalize_hook_t> finalize_hooks;
textual_parser_t() {
add_finalize_hook(finalize_entry);
}
virtual bool test(std::istream& in) const {
return true;
}
void add_finalize_hook(finalize_hook_t func, bool prepend = false) {
if (prepend)
finalize_hooks.push_front(func);
else
finalize_hooks.push_back(func);
}
void remove_finalize_hook(finalize_hook_t func) {
finalize_hooks.remove(func);
}
bool run_finalize_hooks(entry_t * entry) {
for (std::list<finalize_hook_t>::const_iterator i
= finalize_hooks.begin();
i != finalize_hooks.end();
i++)
if (! (*i)(entry))
return false;
return true;
}
virtual unsigned int parse(std::istream& in,
journal_t * journal,
account_t * master = NULL,