diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | NEWS | 10 | ||||
-rwxr-xr-x | acprep | 2 | ||||
-rw-r--r-- | config.cc | 97 | ||||
-rw-r--r-- | config.h | 20 | ||||
-rw-r--r-- | format.h | 2 | ||||
-rw-r--r-- | ledger.texi | 18 | ||||
-rw-r--r-- | main.cc | 22 | ||||
-rw-r--r-- | main.py | 14 | ||||
-rw-r--r-- | option.cc | 67 | ||||
-rw-r--r-- | option.h | 13 | ||||
-rw-r--r-- | pyledger.cc | 2 | ||||
-rw-r--r-- | textual.cc | 3 |
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 \ @@ -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 @@ -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" \ @@ -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 @@ -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); } }; @@ -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 @@ -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(); } @@ -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): @@ -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); } @@ -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(); @@ -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; } |