summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--NEWS10
-rwxr-xr-xacprep2
-rw-r--r--config.cc97
-rw-r--r--config.h20
-rw-r--r--format.h2
-rw-r--r--ledger.texi18
-rw-r--r--main.cc22
-rw-r--r--main.py14
-rw-r--r--option.cc67
-rw-r--r--option.h13
-rw-r--r--pyledger.cc2
-rw-r--r--textual.cc3
13 files changed, 205 insertions, 67 deletions
diff --git a/Makefile.am b/Makefile.am
index 41c25d4e..7115efef 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -24,7 +24,7 @@ if DEBUG
ledger_CXXFLAGS = -DDEBUG_LEVEL=4
endif
ledger_SOURCES = main.cc
-ledger_LDADD = $(LIBOBJS) libledger.la -lboost_python -L/sw/lib/python2.3/config -lpython2.3
+ledger_LDADD = $(LIBOBJS) libledger.la -lboost_python -lpython$(PYTHON_VERSION)
nobase_include_HEADERS = \
amount.h \
diff --git a/NEWS b/NEWS
index 7dfef589..7d4c8b55 100644
--- a/NEWS
+++ b/NEWS
@@ -2,13 +2,17 @@
* 2.0
-- The code base was rewritten for clarity and consistency. It is now
- simpler internally, more robust, and much faster.
+- The code was completely rewritten.
- The most significant feature added are "value expressions". They
are used in several places to indicate what to display, sorting
order, output format, etc. Logic and math operators are supported,
- as well as a few functions. See the documentation for more info.
+ as well as a few functions. See manual.
+
+- If Boost.Python is installed, then ledger can support full Python
+ integration. It can can be used as a module (ledger.so), as well as
+ supporting Python function calls directly from value expressions.
+ See manual.
- If the environment variable LEDGER (or LEDGER_FILE) is used, a
binary cache of that ledger is now kept in ~/.ledger (or
diff --git a/acprep b/acprep
index aa3d01c0..f95ca98f 100755
--- a/acprep
+++ b/acprep
@@ -15,7 +15,7 @@ autoconf
INCDIRS="-I/sw/include -I/usr/include/httpd/xml -I/sw/include/python2.3"
INCDIRS="$INCDIRS -Wno-long-double"
-LIBDIRS="-L/sw/lib"
+LIBDIRS="-L/sw/lib -L/sw/lib/python2.3/config"
if [ "$1" = "--debug" ]; then
./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \
diff --git a/config.cc b/config.cc
index 7a249c2d..108a9e5d 100644
--- a/config.cc
+++ b/config.cc
@@ -6,8 +6,6 @@
namespace ledger {
-config_t config;
-
std::string bal_fmt = "%20T %2_%-n\n";
std::string reg_fmt
= "%D %-.20P %-.22N %12.66t %12.80T\n%/%32|%-.22N %12.66t %12.80T\n";
@@ -18,6 +16,9 @@ std::string print_fmt
std::string equity_fmt
= "\n%D %X%C%P\n%/ %-34N %12t\n";
+config_t config;
+std::list<option_t> config_options;
+
config_t::config_t()
{
if (const char * p = std::getenv("HOME"))
@@ -40,8 +41,11 @@ config_t::config_t()
show_revalued = false;
show_revalued_only = false;
download_quotes = false;
+
use_cache = false;
cache_dirty = false;
+ sort_order = NULL;
+ output_stream = NULL;
}
static void
@@ -167,15 +171,15 @@ void config_t::process_options(const std::string& command,
// Parse the sort_string
- if (! sort_order.get() && ! sort_string.empty()) {
+ if (! sort_order && ! sort_string.empty()) {
try {
std::istringstream stream(sort_string);
- sort_order.reset(parse_value_expr(stream));
+ sort_order = parse_value_expr(stream);
if (stream.peek() != -1) {
throw value_expr_error(std::string("Unexpected character '") +
char(stream.peek()) + "'");
}
- else if (! sort_order.get()) {
+ else if (! sort_order) {
throw error("Failed to parse sort criteria!");
}
}
@@ -217,8 +221,8 @@ void config_t::process_options(const std::string& command,
// Configure the output stream
- if (! output_stream.get() && ! output_file.empty())
- output_stream.reset(new std::ofstream(output_file.c_str()));
+ if (! output_stream && ! output_file.empty())
+ output_stream = new std::ofstream(output_file.c_str());
// Parse the interval specifier, if provided
@@ -612,3 +616,82 @@ OPT_BEGIN(weighted_trend, "Z") {
} OPT_END(weighted_trend);
} // namespace ledger
+
+#ifdef USE_BOOST_PYTHON
+
+#include <boost/python.hpp>
+#include <boost/python/detail/api_placeholder.hpp>
+
+using namespace boost::python;
+using namespace ledger;
+
+void py_process_options(config_t& config, const std::string& command,
+ list args)
+{
+ strings_list strs;
+
+ int l = len(args);
+ for (int i = 0; i < l; i++)
+ strs.push_back(std::string(extract<char *>(args[i])));
+
+ config.process_options(command, strs.begin(), strs.end());
+}
+
+void add_other_option_handlers(const std::list<option_t>& other);
+
+void py_add_config_option_handlers()
+{
+ add_other_option_handlers(config_options);
+}
+
+void export_config()
+{
+ class_< config_t > ("Config")
+ .def_readwrite("price_settings", &config_t::price_settings)
+ .def_readwrite("init_file", &config_t::init_file)
+ .def_readwrite("data_file", &config_t::data_file)
+ .def_readwrite("cache_file", &config_t::cache_file)
+ .def_readwrite("price_db", &config_t::price_db)
+ .def_readwrite("output_file", &config_t::output_file)
+ .def_readwrite("account", &config_t::account)
+ .def_readwrite("predicate", &config_t::predicate)
+ .def_readwrite("display_predicate", &config_t::display_predicate)
+ .def_readwrite("interval_text", &config_t::interval_text)
+ .def_readwrite("format_string", &config_t::format_string)
+ .def_readwrite("date_format", &config_t::date_format)
+ .def_readwrite("sort_string", &config_t::sort_string)
+ .def_readwrite("value_expr", &config_t::value_expr)
+ .def_readwrite("total_expr", &config_t::total_expr)
+ .def_readwrite("pricing_leeway", &config_t::pricing_leeway)
+ .def_readwrite("show_collapsed", &config_t::show_collapsed)
+ .def_readwrite("show_subtotal", &config_t::show_subtotal)
+ .def_readwrite("show_related", &config_t::show_related)
+ .def_readwrite("show_all_related", &config_t::show_all_related)
+ .def_readwrite("show_inverted", &config_t::show_inverted)
+ .def_readwrite("show_empty", &config_t::show_empty)
+ .def_readwrite("days_of_the_week", &config_t::days_of_the_week)
+ .def_readwrite("show_revalued", &config_t::show_revalued)
+ .def_readwrite("show_revalued_only", &config_t::show_revalued_only)
+ .def_readwrite("download_quotes", &config_t::download_quotes)
+
+ .def_readwrite("use_cache", &config_t::use_cache)
+ .def_readwrite("cache_dirty", &config_t::cache_dirty)
+ .def_readwrite("report_interval", &config_t::report_interval)
+ .def_readwrite("format", &config_t::format)
+ .def_readwrite("nformat", &config_t::nformat)
+ .add_property("sort_order",
+ make_getter(&config_t::sort_order,
+ return_value_policy<reference_existing_object>()))
+ .add_property("output_stream",
+ make_getter(&config_t::output_stream,
+ return_value_policy<reference_existing_object>()))
+
+ .def("process_options", py_process_options)
+ ;
+
+ scope().attr("config") = ptr(&config);
+
+ def("add_config_option_handlers", py_add_config_option_handlers);
+}
+
+#endif // USE_BOOST_PYTHON
diff --git a/config.h b/config.h
index 4342d8c1..d481757d 100644
--- a/config.h
+++ b/config.h
@@ -9,6 +9,7 @@
#include <iostream>
#include <memory>
+#include <list>
namespace ledger {
@@ -58,24 +59,35 @@ struct config_t
format_t format;
format_t nformat;
- std::auto_ptr<value_expr_t> sort_order;
- std::auto_ptr<std::ostream> output_stream;
+ value_expr_t * sort_order;
+ std::ostream * output_stream;
config_t();
+ config_t(const config_t&) {
+ assert(0);
+ }
+
+ ~config_t() {
+ if (sort_order)
+ delete sort_order;
+ if (output_stream)
+ delete output_stream;
+ }
void process_options(const std::string& command,
strings_list::iterator arg,
strings_list::iterator args_end);
};
-extern config_t config;
+extern config_t config;
+extern std::list<option_t> config_options;
void option_help(std::ostream& out);
struct declared_option_handler : public option_handler {
declared_option_handler(const std::string& label,
const std::string& opt_chars) {
- register_option(label, opt_chars, *this);
+ add_option_handler(config_options, label, opt_chars, *this);
}
};
diff --git a/format.h b/format.h
index df5dc49a..b507fa20 100644
--- a/format.h
+++ b/format.h
@@ -51,6 +51,7 @@ struct element_t
struct format_t
{
+ std::string format_string;
element_t * elements;
static std::string date_format;
@@ -69,6 +70,7 @@ struct format_t
if (elements)
delete elements;
elements = parse_elements(_format);
+ format_string = _format;
}
static element_t * parse_elements(const std::string& fmt);
diff --git a/ledger.texi b/ledger.texi
index 212d3f39..b83bcbf0 100644
--- a/ledger.texi
+++ b/ledger.texi
@@ -24,6 +24,7 @@
* Introduction::
* Running Ledger::
* Keeping a ledger::
+* Extending with Python::
@end menu
@node Introduction, Running Ledger, Top, Top
@@ -1100,7 +1101,7 @@ special entries used by timeclock, but ignored by ledger.
@end table
-@node Keeping a ledger, , Running Ledger, Top
+@node Keeping a ledger, Extending with Python, Running Ledger, Top
@chapter Keeping a ledger
The most important part of accounting is keeping a good ledger. If
@@ -2099,6 +2100,21 @@ ledger, with the attached prefix ``Billable'':
Receivable:ClientOne
@end example
+@node Extending with Python, , Keeping a ledger, Top
+@chapter Extending with Python
+
+Ledger fully supports Python as an extension language. It may be used
+in a few different forms, which fall into three basic categories:
+
+@enumerate
+@item
+Defining Python functions to use in value expressions
+@item
+Using the ledger library as a Python module
+@item
+Setting up custom initialization using Python
+@end enumerate
+
@c Page published by Emacs Muse ends here
@contents
@bye
diff --git a/main.cc b/main.cc
index cbdf7bed..a2640a90 100644
--- a/main.cc
+++ b/main.cc
@@ -168,9 +168,9 @@ chain_formatters(const std::string& command,
// sort_transactions will sort all the transactions it sees, based
// on the `sort_order' value expression.
- if (config.sort_order.get())
+ if (config.sort_order)
ptrs.push_back(formatter =
- new sort_transactions(formatter, config.sort_order.get()));
+ new sort_transactions(formatter, config.sort_order));
// changed_value_transactions adds virtual transactions to the
// list to account for changes in market value of commodities,
@@ -235,7 +235,7 @@ int parse_and_report(int argc, char * argv[], char * envp[])
TIMER_START(process_opts);
std::list<std::string> args;
- process_arguments(argc - 1, argv + 1, false, args);
+ process_arguments(config_options, argc - 1, argv + 1, false, args);
if (args.empty()) {
option_help(std::cerr);
@@ -245,17 +245,17 @@ int parse_and_report(int argc, char * argv[], char * envp[])
config.use_cache = config.data_file.empty();
- process_environment(envp, "LEDGER_");
+ process_environment(config_options, envp, "LEDGER_");
#if 1
// These are here for backwards compatability, but are deprecated.
if (const char * p = std::getenv("LEDGER"))
- process_option("file", p);
+ process_option(config_options, "file", p);
if (const char * p = std::getenv("PRICE_HIST"))
- process_option("price-db", p);
+ process_option(config_options, "price-db", p);
if (const char * p = std::getenv("PRICE_EXP"))
- process_option("price-exp", p);
+ process_option(config_options, "price-exp", p);
#endif
TIMER_STOP(process_opts);
@@ -317,7 +317,7 @@ int parse_and_report(int argc, char * argv[], char * envp[])
formatter = new set_account_value;
formatter = chain_formatters(command, formatter, formatter_ptrs);
} else {
- std::ostream& out(config.output_stream.get() ?
+ std::ostream& out(config.output_stream ?
*config.output_stream : std::cout);
formatter = new format_transactions(out, config.format, config.nformat);
formatter = chain_formatters(command, formatter, formatter_ptrs);
@@ -332,14 +332,14 @@ int parse_and_report(int argc, char * argv[], char * envp[])
// For the balance and equity reports, output the sum totals.
- std::ostream& out(config.output_stream.get() ?
+ std::ostream& out(config.output_stream ?
*config.output_stream : std::cout);
if (command == "b") {
format_account acct_formatter(out, config.format,
config.display_predicate);
sum_accounts(*journal->master);
- walk_accounts(*journal->master, acct_formatter, config.sort_order.get());
+ walk_accounts(*journal->master, acct_formatter, config.sort_order);
acct_formatter.flush();
if (journal->master->data) {
@@ -355,7 +355,7 @@ int parse_and_report(int argc, char * argv[], char * envp[])
format_equity acct_formatter(out, config.format, config.nformat,
config.display_predicate);
sum_accounts(*journal->master);
- walk_accounts(*journal->master, acct_formatter, config.sort_order.get());
+ walk_accounts(*journal->master, acct_formatter, config.sort_order);
acct_formatter.flush();
}
diff --git a/main.py b/main.py
index 7427961d..e30e229e 100644
--- a/main.py
+++ b/main.py
@@ -9,16 +9,18 @@ def hello (str):
def goodbye (str):
print "Goodbye:", str
-register_option ("hello", "h:", hello)
-register_option ("goodbye", "g:", goodbye)
+add_config_option_handlers ()
+add_option_handler ("hello", ":", hello)
+add_option_handler ("goodbye", ":", goodbye)
+
args = process_arguments (sys.argv[1:])
-process_environment (os.environ, "TEST_")
+process_environment (os.environ, "LEDGER_")
-parser = TextualParser ()
-register_parser (parser)
+text_parser = TextualParser ()
+register_parser (text_parser)
journal = Journal ()
-parse_journal_file (args[0], journal)
+print parse_journal_file (args[0], journal), "entries"
class FormatTransaction (TransactionHandler):
def __init__ (self, fmt):
diff --git a/option.cc b/option.cc
index f4053e4e..4679e1ad 100644
--- a/option.cc
+++ b/option.cc
@@ -6,11 +6,10 @@
#include "util.h"
-static std::list<option_t> options;
-
-void register_option(const std::string& label,
- const std::string& opt_chars,
- option_handler& option)
+void add_option_handler(std::list<option_t>& options,
+ const std::string& label,
+ const std::string& opt_chars,
+ option_handler& option)
{
DEBUG_PRINT("ledger.memory.ctors", "ctor option_handler");
@@ -40,15 +39,18 @@ void register_option(const std::string& label,
options.push_back(opt);
}
-static inline void process_option(const option_t& opt,
- const char * arg = NULL) {
- if (! opt.handler->handled) {
- (*opt.handler)(arg);
- opt.handler->handled = true;
+namespace {
+ inline void process_option(const option_t& opt,
+ const char * arg = NULL) {
+ if (! opt.handler->handled) {
+ (*opt.handler)(arg);
+ opt.handler->handled = true;
+ }
}
}
-bool process_option(const std::string& opt, const char * arg)
+bool process_option(std::list<option_t>& options,
+ const std::string& opt, const char * arg)
{
for (std::list<option_t>::iterator i = options.begin();
i != options.end();
@@ -65,7 +67,8 @@ bool process_option(const std::string& opt, const char * arg)
return false;
}
-void process_arguments(int argc, char ** argv, const bool anywhere,
+void process_arguments(std::list<option_t>& options,
+ int argc, char ** argv, const bool anywhere,
std::list<std::string>& args)
{
int index = 0;
@@ -138,7 +141,8 @@ void process_arguments(int argc, char ** argv, const bool anywhere,
}
}
-void process_environment(char ** envp, const std::string& tag)
+void process_environment(std::list<option_t>& options,
+ char ** envp, const std::string& tag)
{
const char * tag_p = tag.c_str();
int tag_len = tag.length();
@@ -157,7 +161,7 @@ void process_environment(char ** envp, const std::string& tag)
*r = '\0';
if (*q == '=')
- process_option(buf, q + 1);
+ process_option(options, buf, q + 1);
}
}
@@ -165,7 +169,6 @@ void process_environment(char ** envp, const std::string& tag)
#include <boost/python.hpp>
#include <boost/python/detail/api_placeholder.hpp>
-#include <Python.h>
#include <vector>
using namespace boost::python;
@@ -180,17 +183,27 @@ struct func_option_wrapper : public option_handler
}
};
-static std::list<func_option_wrapper> wrappers;
+namespace {
+ std::list<func_option_wrapper> wrappers;
+ std::list<option_t> options;
+}
-void py_register_option(const std::string& long_opt,
+void py_add_option_handler(const std::string& long_opt,
const std::string& short_opt, object func)
{
wrappers.push_back(func_option_wrapper(func));
- register_option(long_opt, short_opt, wrappers.back());
+ add_option_handler(options, long_opt, short_opt, wrappers.back());
}
-bool (*process_option_1)(const std::string& opt, const char * arg)
- = process_option;
+void add_other_option_handlers(const std::list<option_t>& other)
+{
+ options.insert(options.begin(), other.begin(), other.end());
+}
+
+bool py_process_option(const std::string& opt, const char * arg)
+{
+ return process_option(options, opt, arg);
+}
list py_process_arguments(list args, bool anywhere = false)
{
@@ -201,7 +214,7 @@ list py_process_arguments(list args, bool anywhere = false)
strs.push_back(extract<char *>(args[i]));
std::list<std::string> newargs;
- process_arguments(strs.size(), &strs.front(), anywhere, newargs);
+ process_arguments(options, strs.size(), &strs.front(), anywhere, newargs);
list py_newargs;
for (std::list<std::string>::iterator i = newargs.begin();
@@ -211,6 +224,9 @@ list py_process_arguments(list args, bool anywhere = false)
return py_newargs;
}
+BOOST_PYTHON_FUNCTION_OVERLOADS(py_proc_args_overloads,
+ py_process_arguments, 1, 2)
+
void py_process_environment(object env, const std::string& tag)
{
std::vector<char *> strs;
@@ -227,16 +243,13 @@ void py_process_environment(object env, const std::string& tag)
strs.push_back(const_cast<char *>(storage.back().c_str()));
}
- process_environment(&strs.front(), tag);
+ process_environment(options, &strs.front(), tag);
}
-BOOST_PYTHON_FUNCTION_OVERLOADS(py_proc_args_overloads,
- py_process_arguments, 1, 2)
-
void export_option()
{
- def("register_option", py_register_option);
- def("process_option", process_option_1);
+ def("add_option_handler", py_add_option_handler);
+ def("process_option", py_process_option);
def("process_arguments", py_process_arguments, py_proc_args_overloads());
def("process_environment", py_process_environment);
}
diff --git a/option.h b/option.h
index fe073274..49f228cd 100644
--- a/option.h
+++ b/option.h
@@ -19,11 +19,14 @@ struct option_t {
option_t() : short_opt(0), wants_arg(false), handler(NULL) {}
};
-void register_option(const std::string& label,
- const std::string& opt_chars, option_handler& option);
-bool process_option(const std::string& opt, const char * arg = NULL);
-void process_arguments(int argc, char ** argv, const bool anywhere,
+void add_option_handler(std::list<option_t>& options, const std::string& label,
+ const std::string& opt_chars, option_handler& option);
+bool process_option(std::list<option_t>& options,
+ const std::string& opt, const char * arg = NULL);
+void process_arguments(std::list<option_t>& options,
+ int argc, char ** argv, const bool anywhere,
std::list<std::string>& args);
-void process_environment(char ** envp, const std::string& tag);
+void process_environment(std::list<option_t>& options,
+ char ** envp, const std::string& tag);
#endif // _OPTION_H
diff --git a/pyledger.cc b/pyledger.cc
index d0f2a4be..923d8a88 100644
--- a/pyledger.cc
+++ b/pyledger.cc
@@ -17,6 +17,7 @@ void export_qif();
void export_gnucash();
#endif
void export_option();
+void export_config();
void export_walk();
void export_format();
void export_valexpr();
@@ -35,6 +36,7 @@ BOOST_PYTHON_MODULE(ledger) {
export_gnucash();
#endif
export_option();
+ export_config();
export_walk();
export_format();
export_valexpr();
diff --git a/textual.cc b/textual.cc
index 6e80884b..57cb0089 100644
--- a/textual.cc
+++ b/textual.cc
@@ -5,6 +5,7 @@
#include "valexpr.h"
#include "error.h"
#include "option.h"
+#include "config.h"
#include "timing.h"
#include "util.h"
#ifdef USE_BOOST_PYTHON
@@ -513,7 +514,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
in.getline(line, MAX_LINE);
linenum++;
char * p = skip_ws(line);
- process_option(opt, *p == '\n' ? NULL : p);
+ process_option(config_options, opt, *p == '\n' ? NULL : p);
break;
}