diff options
author | John Wiegley <johnw@newartisans.com> | 2009-01-31 18:52:34 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-01-31 18:52:34 -0400 |
commit | 9d267fa1331a570e2b4c978f0b35a107a47b51c1 (patch) | |
tree | ed8c5bc7d5b3b2024fb4fee80f4ec86332fb277f | |
parent | 75daee6a5df8154ecd6011f93af746105529a13d (diff) | |
download | fork-ledger-9d267fa1331a570e2b4c978f0b35a107a47b51c1.tar.gz fork-ledger-9d267fa1331a570e2b4c978f0b35a107a47b51c1.tar.bz2 fork-ledger-9d267fa1331a570e2b4c978f0b35a107a47b51c1.zip |
Inspired by Omari Norman, I've rewritten main.cc so it's easy to approach.
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | src/account.cc | 4 | ||||
-rw-r--r-- | src/item.cc | 2 | ||||
-rw-r--r-- | src/ledger.h | 8 | ||||
-rw-r--r-- | src/main.cc | 406 | ||||
-rw-r--r-- | src/ofx.cc | 4 | ||||
-rw-r--r-- | src/report.cc | 36 | ||||
-rw-r--r-- | src/report.h | 2 | ||||
-rw-r--r-- | src/session.cc | 77 | ||||
-rw-r--r-- | src/session.h | 27 | ||||
-rw-r--r-- | src/work.cc | 308 | ||||
-rw-r--r-- | src/work.h | 65 |
12 files changed, 561 insertions, 381 deletions
diff --git a/Makefile.am b/Makefile.am index 994e6e35..ef2dfa56 100644 --- a/Makefile.am +++ b/Makefile.am @@ -151,6 +151,7 @@ pkginclude_HEADERS = \ src/reconcile.h \ src/quotes.h \ \ + src/work.h \ src/ledger.h \ \ python/pyledger.h \ @@ -194,7 +195,7 @@ ledger_CPPFLAGS = $(lib_cppflags) if HAVE_BOOST_PYTHON ledger_CPPFLAGS += -I$(srcdir)/python endif -ledger_SOURCES = src/main.cc +ledger_SOURCES = src/main.cc src/work.cc ledger_LDADD = $(LIBOBJS) $(lib_LTLIBRARIES) if HAVE_BOOST_PYTHON ledger_LDADD += libledger_python.la diff --git a/src/account.cc b/src/account.cc index a93d4266..614d7119 100644 --- a/src/account.cc +++ b/src/account.cc @@ -197,7 +197,7 @@ expr_t::ptr_op_t account_t::lookup(const string& name) break; } - return session_t::current->current_report->lookup(name); + return session_t::current->report->lookup(name); } bool account_t::valid() const @@ -240,7 +240,7 @@ void account_t::calculate_sums() } call_scope_t args(*this); - value_t amount(session_t::current->current_report->get_amount_expr(args)); + value_t amount(session_t::current->report->get_amount_expr(args)); if (! amount.is_null()) { add_or_set_value(xd.total, amount); xd.total_count += xd.count; diff --git a/src/item.cc b/src/item.cc index bdd72f3b..185ee033 100644 --- a/src/item.cc +++ b/src/item.cc @@ -128,7 +128,7 @@ expr_t::ptr_op_t item_t::lookup(const string& name) break; } - return session_t::current->current_report->lookup(name); + return session_t::current->report->lookup(name); } bool item_t::valid() const diff --git a/src/ledger.h b/src/ledger.h index 759ebd26..66fa2c8f 100644 --- a/src/ledger.h +++ b/src/ledger.h @@ -41,6 +41,7 @@ #define _LEDGER_H #include <utils.h> +#include <option.h> #include <value.h> @@ -67,4 +68,11 @@ #include <reconcile.h> #include <quotes.h> +#if defined(HAVE_BOOST_PYTHON) +#include <pyinterp.h> +#define LEDGER_SESSION_T python_interpreter_t +#else +#define LEDGER_SESSION_T session_t +#endif + #endif // _LEDGER_H diff --git a/src/main.cc b/src/main.cc index faeec6ac..7d43849b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -29,335 +29,129 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "session.h" -#include "report.h" -#include "option.h" -#include "help.h" -#include "pyinterp.h" +#include <ledger.h> -#include "textual.h" -#include "qif.h" -#include "xml.h" -#include "gnucash.h" -#ifdef HAVE_LIBOFX -#include "ofx.h" -#endif +#include "work.h" // this is where the top-level code is -namespace ledger { - int read_and_report(ledger::report_t& report, - int argc, char * argv[], char * envp[]) - { - using namespace ledger; - - session_t& session(report.session); - - // Setup global defaults - - optional<path> home; - if (const char * home_var = std::getenv("HOME")) - home = home_var; - - session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; - session.price_db = home ? *home / ".pricedb" : "./.pricedb"; - session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache"; - - // Process the environment settings - - TRACE_START(environment, 1, "Processed environment variables"); - - process_environment(const_cast<const char **>(envp), "LEDGER_", report); - -#if 1 - // These are here for backwards compatability, but are deprecated. - - if (const char * p = std::getenv("LEDGER")) - process_option("file", report, p); - if (const char * p = std::getenv("LEDGER_INIT")) - process_option("init-file", report, p); - if (const char * p = std::getenv("PRICE_HIST")) - process_option("price-db", report, p); - if (const char * p = std::getenv("PRICE_EXP")) - process_option("price-exp", report, p); -#endif - - TRACE_FINISH(environment, 1); - - // Read the initialization file - - TRACE_START(init, 1, "Read initialization file"); - - session.read_init(); - - TRACE_FINISH(init, 1); - - // Handle the command-line arguments - - TRACE_START(arguments, 1, "Processed command-line arguments"); - - strings_list args; - process_arguments(argc - 1, argv + 1, report, args); - - if (args.empty()) { - ledger::help(std::cout); - return 1; - } - strings_list::iterator arg = args.begin(); - - if (! session.cache_file) - session.use_cache = false; - else if (session.use_cache) - session.use_cache = ! session.data_file.empty() && session.price_db; - - DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache); - - if (session.data_file == *session.cache_file) - session.use_cache = false; - - DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache); - - INFO("Initialization file is " << session.init_file->string()); - INFO("Price database is " << session.price_db->string()); - INFO("Binary cache is " << session.cache_file->string()); - INFO("Journal file is " << session.data_file.string()); - - if (! session.use_cache) - INFO("Binary cache mechanism will not be used"); - - TRACE_FINISH(arguments, 1); - - // Read the command word and see if it's any of the debugging commands - // that Ledger supports. - - string verb = *arg++; - - function_t command; - if (expr_t::ptr_op_t def = report.lookup(string("ledger_precmd_") + verb)) - command = def->as_function(); - - // Parse the initialization file, which can only be textual; then - // parse the journal data. But only do this if there was no - // "pre-command", which are always executed without doing any - // parsing. - - if (! command) { - INFO_START(journal, "Read journal file"); - - journal_t& journal(*session.create_journal()); - - std::size_t count = session.read_data(journal, report.account); - if (count == 0) - throw_(parse_error, "Failed to locate any journal entries; " - "did you specify a valid file with -f?"); - - INFO_FINISH(journal); - - INFO("Found " << count << " entries"); - - TRACE_FINISH(entry_text, 1); - TRACE_FINISH(entry_details, 1); - TRACE_FINISH(entry_xacts, 1); - TRACE_FINISH(entries, 1); - TRACE_FINISH(session_parser, 1); - TRACE_FINISH(parsing_total, 1); - - // Lookup the command object corresponding to the command verb. +int main(int argc, char * argv[], char * envp[]) +{ + using namespace ledger; - if (expr_t::ptr_op_t def = report.lookup(string("ledger_cmd_") + verb)) - command = def->as_function(); - } + // The very first thing we do is handle some very special command-line + // options, since they affect how the whole environment is setup: + // + // --verify ; turns on memory tracing + // --verbose ; turns on logging + // --debug CATEGORY ; turns on debug logging + // --trace LEVEL ; turns on trace logging + handle_debug_options(argc, argv); - if (! command) - throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); + IF_VERIFY() + initialize_memory_tracing(); -#if 1 - // Patch up some of the reporting options based on what kind of - // command it was. + // Initialize the global C++ environment + std::ios::sync_with_stdio(false); + filesystem::path::default_name_check(filesystem::portable_posix_name); - // jww (2008-08-14): This code really needs to be rationalized away - // for 3.0. + // Initialization of Ledger can now begin. The whole series of operations + // is contained in a try block, so we can report errors in a nicer fashion. + INFO("Ledger starting"); - if (verb == "print" || verb == "entry" || verb == "dump") { - report.show_related = true; - report.show_all_related = true; - } - else if (verb == "equity") { - report.show_subtotal = true; + session_t * session = NULL; + int status = 1; + try { + // Create the session object, which maintains nearly all state relating to + // this invocation of Ledger. + session = new LEDGER_SESSION_T; + set_session_context(session); + + // Register all known journal parsers. The order of these is important. + register_journal_parsers(*session); + + // Create the report object, which maintains state relating to each + // command invocation. Because we're running this from main() the + // distinction between session and report doesn't matter, but if a GUI + // were calling into Ledger, it would have one session object, with a + // separate report object for each report it generated. + session->report.reset(new report_t(*session)); + report_t& report(*session->report.get()); + + // Read user option settings, first in the environment, then from the + // user's initialization file and then from the command-line. The first + // non-option argument thereafter is the "command verb". + read_environment_settings(report, envp); + session->read_init(); + + // Notify the session object that all option handlers invoked beyond this + // point came from the command-line + session->now_at_command_line(true); + + strings_list args = read_command_line_arguments(report, argc, argv); + string_iterator arg = args.begin(); + string verb = *arg++; + + // Look for a precommand first, which is defined as any defined function + // whose name starts with "ledger_precmd_". The difference between a + // precommand and a regular command is that precommands ignore the journal + // data file completely, nor is the user's init file read. + // + // Here are some examples: + // + // parse STRING ; show how a value expression is parsed + // eval STRING ; simply evaluate a value expression + // format STRING ; show how a format string is parsed + // + // If such a command is found, create the output stream for the result and + // then invoke the command. + if (function_t command = look_for_precommand(report, verb)) { + create_output_stream(report); + invoke_command_verb(report, command, arg, args.end()); } - else if (report.show_related) { - if (verb[0] == 'r') { - report.show_inverted = true; - } else { - report.show_subtotal = true; - report.show_all_related = true; + else if (function_t command = look_for_command(report, verb)) { + // Parse the user's journal files. + if (journal_t * journal = read_journal_files(*session)) { + normalize_report_options(report, verb); // this is a total hack + + // Create the output stream (it might be a file, the console or a + // PAGER subprocess) and invoke the report command. + create_output_stream(report); + invoke_command_verb(report, command, arg, args.end()); + + // Write out a binary cache of the journal data, if needed and + // appropriate to do so + write_binary_cache(*session, journal); } } - - if (verb[0] != 'b' && verb[0] != 'r') - amount_t::keep_base = true; - - // Setup the default value for the display predicate - - if (report.display_predicate.empty()) { - if (verb[0] == 'b') { - if (! report.show_empty) - report.display_predicate = "total"; - if (! report.show_subtotal) { - if (! report.display_predicate.empty()) - report.display_predicate += "&"; - report.display_predicate += "depth<=1"; - } - } - else if (verb == "equity") { - report.display_predicate = "amount_expr"; // jww (2008-08-14): ??? - } - else if (verb[0] == 'r' && ! report.show_empty) { - report.display_predicate = "amount"; - } + else { + throw_(std::logic_error, "Unrecognized command '" << verb << "'"); } -#endif - - // Now setup the various formatting strings - - // jww (2008-08-14): I hear a song, and it's sound is "HaAaaCcK" - -#if 0 - if (! date_output_format.empty()) - date_t::output_format = date_output_format; -#endif - - amount_t::keep_price = report.keep_price; - amount_t::keep_date = report.keep_date; - amount_t::keep_tag = report.keep_tag; - - if (! report.report_period.empty() && ! report.sort_all) - report.entry_sort = true; - - // Setup the output stream, which might involve invoking the pager - - report.output_stream.initialize(report.output_file, report.pager_path); - - // Create an argument scope containing the report command's - // arguments, and then invoke the command. - - call_scope_t command_args(report); - for (strings_list::iterator i = arg; i != args.end(); i++) - command_args.push_back(string_value(*i)); - - INFO_START(command, "Did user command '" << verb << "'"); - - command(command_args); - - INFO_FINISH(command); - -#if 0 - // Write out the binary cache, if need be - - if (session.use_cache && session.cache_dirty && session.cache_file) { - TRACE_START(binary_cache, 1, "Wrote binary journal file"); - - ofstream stream(*session.cache_file); - journal.write(stream); - - TRACE_FINISH(binary_cache, 1); - } -#endif - - return 0; - } -} - -int main(int argc, char * argv[], char * envp[]) -{ - int status = 1; - - for (int i = 1; i < argc; i++) - if (argv[i][0] == '-') { - if (std::strcmp(argv[i], "--verify") == 0) { -#if defined(VERIFY_ON) - ledger::verify_enabled = true; -#endif - } - else if (std::strcmp(argv[i], "--verbose") == 0 || - std::strcmp(argv[i], "-v") == 0) { -#if defined(LOGGING_ON) - ledger::_log_level = ledger::LOG_INFO; -#endif - } - else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { -#if defined(DEBUG_ON) - ledger::_log_level = ledger::LOG_DEBUG; - ledger::_log_category = argv[i + 1]; - i++; -#endif - } - else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { -#if defined(TRACING_ON) - ledger::_log_level = ledger::LOG_TRACE; - try { - ledger::_trace_level = boost::lexical_cast<int>(argv[i + 1]); - } - catch (const boost::bad_lexical_cast& e) { - std::cerr << "Argument to --trace must be an integer." - << std::endl; - return 1; - } - i++; -#endif - } - } - - IF_VERIFY() - ledger::initialize_memory_tracing(); - - try { - std::ios::sync_with_stdio(false); - - boost::filesystem::path::default_name_check - (boost::filesystem::portable_posix_name); - - INFO("Ledger starting"); - -#if defined(HAVE_BOOST_PYTHON) - std::auto_ptr<ledger::session_t> session(new ledger::python_interpreter_t); -#else - std::auto_ptr<ledger::session_t> session(new ledger::session_t); -#endif - - ledger::set_session_context(session.get()); - -#if 0 - session->register_parser(new ledger::journal_t::binary_parser_t); -#endif - session->register_parser(new ledger::xml_parser_t); - session->register_parser(new ledger::gnucash_parser_t); -#ifdef HAVE_LIBOFX - session->register_parser(new ledger::ofx_parser_t); -#endif - session->register_parser(new ledger::qif_parser_t); - session->register_parser(new ledger::textual_parser_t); - - session->current_report.reset(new ledger::report_t(*session.get())); - - status = read_and_report(*session->current_report.get(), argc, argv, envp); - - if (! DO_VERIFY()) - session.release(); // don't free anything! just let it leak + // If we got here, everything succeeded just fine. Ledger uses exceptions + // to notify of any error conditions, so if you're using gdb, type "catch + // throw" to find the source of any errors. + status = 0; } catch (const std::exception& err) { std::cout.flush(); - std::cerr << ledger::error_context() - << "Error: " << err.what() << std::endl; + std::cerr << error_context() << "Error: " << err.what() << std::endl; } catch (int _status) { status = _status; } + // If memory verification is being performed (which can be very slow), clean + // up everything by closing the session and deleting the session object, and + // then shutting down the memory tracing subsystem. Otherwise, let it all + // leak because we're about to exit anyway. IF_VERIFY() { + set_session_context(NULL); + if (session != NULL) + checked_delete(session); + INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); - ledger::set_session_context(); - ledger::shutdown_memory_tracing(); + shutdown_memory_tracing(); } else { + // Don't free anything, just let it all leak. INFO("Ledger ended"); } @@ -34,6 +34,8 @@ #include "account.h" #include "session.h" +#if defined(HAVE_LIBOFX) + #include <libofx.h> namespace ledger { @@ -261,3 +263,5 @@ std::size_t ofx_parser_t::parse(std::istream& in, } } // namespace ledger + +#endif HAVE_LIBOFX diff --git a/src/report.cc b/src/report.cc index 37ac1912..e811666a 100644 --- a/src/report.cc +++ b/src/report.cc @@ -488,38 +488,7 @@ namespace { out << std::endl << "--- Expression tree ---" << std::endl; expr.dump(out); - out << std::endl << "--- Calculated value ---" << std::endl; - expr.calc(args).print(out); - out << std::endl; - - return 0L; - } - - value_t compile_command(call_scope_t& args) - { - var_t<string> arg(args, 0); - - if (! arg) { - throw std::logic_error("Usage: compile TEXT"); - return 1L; - } - - report_t& report(find_scope<report_t>(args)); - std::ostream& out(report.output_stream); - - out << "--- Input text ---" << std::endl; - out << *arg << std::endl; - - out << std::endl << "--- Text as parsed ---" << std::endl; - expr_t expr(*arg); - expr.print(out); - out << std::endl; - - out << std::endl << "--- Expression tree ---" << std::endl; - expr.dump(out); - expr.compile(args); - out << std::endl << "--- Compiled tree ---" << std::endl; expr.dump(out); @@ -680,11 +649,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name) return WRAP_FUNCTOR(period_command); break; - case 'c': - if (std::strcmp(p, "compile") == 0) - return WRAP_FUNCTOR(compile_command); - break; - case 'e': if (std::strcmp(p, "eval") == 0) return WRAP_FUNCTOR(eval_command); diff --git a/src/report.h b/src/report.h index b5103e72..d0a29f75 100644 --- a/src/report.h +++ b/src/report.h @@ -735,6 +735,8 @@ public: } value_t option_price_db_(call_scope_t& args) { // : + // jww (2009-01-31): This, and several of the other option handlers, + // should be in the session object. session.price_db = args[0].as_string(); return true; } diff --git a/src/session.cc b/src/session.cc index 70925ea7..e45cfbc6 100644 --- a/src/session.cc +++ b/src/session.cc @@ -70,7 +70,12 @@ void release_session_context() } session_t::session_t() - : register_format + : next_data_file_from_command_line(false), + saw_data_file_from_command_line(false), + next_price_db_from_command_line(false), + saw_price_db_from_command_line(false), + + register_format ("%-.9(date) %-.20(payee) %-.23(account(23)) %!12(print_balance(amount_expr, 12, 67)) " "%!12(print_balance(display_total, 12, 80, true))\n%/" "%31|%-.23(account(23)) %!12(print_balance(amount_expr, 12, 67)) " @@ -116,6 +121,14 @@ session_t::session_t() master(new account_t(NULL, "")) { TRACE_CTOR(session_t, ""); + + optional<path> home; + if (const char * home_var = std::getenv("HOME")) + home = home_var; + + init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; + price_db = home ? *home / ".pricedb" : "./.pricedb"; + cache_file = home ? *home / ".ledger-cache" : "./.ledger-cache"; } session_t::~session_t() @@ -159,6 +172,8 @@ void session_t::read_init() if (! exists(*init_file)) throw_(std::logic_error, "Cannot read init file" << *init_file); + TRACE_START(init, 1, "Read initialization file"); + ifstream init(*init_file); journal_t temp; @@ -167,12 +182,14 @@ void session_t::read_init() temp.period_entries.size() > 0) throw_(parse_error, "Entries found in initialization file '" << init_file << "'"); + + TRACE_FINISH(init, 1); } std::size_t session_t::read_data(journal_t& journal, const string& master_account) { - if (data_file.empty()) + if (data_files.empty()) throw_(parse_error, "No journal file was specified (please use -f)"); TRACE_START(session_parser, 1, "Parsed journal file"); @@ -210,33 +227,39 @@ std::size_t session_t::read_data(journal_t& journal, } } - DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string()); - if (data_file == "-") { - use_cache = false; - journal.sources.push_back("/dev/stdin"); - - // To avoid problems with stdin and pipes, etc., we read the entire - // file in beforehand into a memory buffer, and then parcel it out - // from there. - std::ostringstream buffer; - - while (std::cin.good() && ! std::cin.eof()) { - static char line[8192]; - std::cin.read(line, 8192); - std::streamsize count = std::cin.gcount(); - buffer.write(line, count); - } - buffer.flush(); - std::istringstream buf_in(buffer.str()); + foreach (const path& pathname, data_files) { + DEBUG("ledger.cache", "rejected cache, parsing " << pathname.string()); + if (pathname == "-") { + use_cache = false; + journal.sources.push_back("/dev/stdin"); - entry_count += read_journal(journal, buf_in, "/dev/stdin", acct); - } - else if (exists(data_file)) { - entry_count += read_journal(journal, data_file, acct); - if (journal.price_db) - journal.sources.push_back(*journal.price_db); - clean_accounts(); + // To avoid problems with stdin and pipes, etc., we read the entire + // file in beforehand into a memory buffer, and then parcel it out + // from there. + std::ostringstream buffer; + + while (std::cin.good() && ! std::cin.eof()) { + static char line[8192]; + std::cin.read(line, 8192); + std::streamsize count = std::cin.gcount(); + buffer.write(line, count); + } + buffer.flush(); + + std::istringstream buf_in(buffer.str()); + + entry_count += read_journal(journal, buf_in, "/dev/stdin", acct); + } + else if (exists(pathname)) { + entry_count += read_journal(journal, pathname, acct); + if (journal.price_db) + journal.sources.push_back(*journal.price_db); + clean_accounts(); + } + else { + throw_(parse_error, "Could not open journal file '" << pathname << "'"); + } } } diff --git a/src/session.h b/src/session.h index 852f3281..5a05d42c 100644 --- a/src/session.h +++ b/src/session.h @@ -71,12 +71,16 @@ class session_t : public noncopyable, public scope_t public: static session_t * current; - scoped_ptr<report_t> current_report; + scoped_ptr<report_t> report; - path data_file; + std::list<path> data_files; + bool next_data_file_from_command_line; + bool saw_data_file_from_command_line; optional<path> init_file; optional<path> cache_file; - optional<path> price_db; + optional<path> price_db; + bool next_price_db_from_command_line; + bool saw_price_db_from_command_line; string register_format; string wide_register_format; @@ -114,6 +118,11 @@ public: session_t(); virtual ~session_t(); + void now_at_command_line(const bool truth) { + next_data_file_from_command_line = truth; + next_price_db_from_command_line = truth; + } + journal_t * create_journal() { journal_t * journal = new journal_t; journals.push_back(journal); @@ -243,11 +252,13 @@ See LICENSE file included with the distribution for details and disclaimer.\n"; value_t option_file_(call_scope_t& args) { // f assert(args.size() == 1); - - // jww (2008-08-13): Add support for multiple files - data_file = args[0].as_string(); - use_cache = false; - + if (next_data_file_from_command_line && + ! saw_data_file_from_command_line) { + data_files.clear(); + use_cache = false; + saw_data_file_from_command_line = true; + } + data_files.push_back(args[0].as_string()); return true; } }; diff --git a/src/work.cc b/src/work.cc new file mode 100644 index 00000000..41f081ef --- /dev/null +++ b/src/work.cc @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2003-2009, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <ledger.h> + +#include "work.h" + +namespace ledger { + +void handle_debug_options(int argc, char * argv[]) +{ + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + if (std::strcmp(argv[i], "--verify") == 0) { +#if defined(VERIFY_ON) + verify_enabled = true; // global in utils.h +#endif + } + else if (std::strcmp(argv[i], "--verbose") == 0 || + std::strcmp(argv[i], "-v") == 0) { +#if defined(LOGGING_ON) + _log_level = LOG_INFO; // global in utils.h +#endif + } + else if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) { +#if defined(DEBUG_ON) + _log_level = LOG_DEBUG; // global in utils.h + _log_category = argv[i + 1]; // global in utils.h + i++; +#endif + } + else if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) { +#if defined(TRACING_ON) + _log_level = LOG_TRACE; // global in utils.h + try { + // global in utils.h + _trace_level = boost::lexical_cast<int>(argv[i + 1]); + } + catch (const boost::bad_lexical_cast& e) { + std::cerr << "Argument to --trace must be an integer." + << std::endl; + throw int(1); + } + i++; +#endif + } + } + } +} + +void register_journal_parsers(session_t& session) +{ +#if 0 + session.register_parser(new journal_t::binary_parser_t); +#endif + session.register_parser(new xml_parser_t); + session.register_parser(new gnucash_parser_t); +#ifdef HAVE_LIBOFX + session.register_parser(new ofx_parser_t); +#endif + session.register_parser(new qif_parser_t); + session.register_parser(new textual_parser_t); +} + +void read_environment_settings(report_t& report, char * envp[]) +{ + TRACE_START(environment, 1, "Processed environment variables"); + + process_environment(const_cast<const char **>(envp), "LEDGER_", + report); + +#if 1 + // These are here for backwards compatability, but are deprecated. + + if (const char * p = std::getenv("LEDGER")) + process_option("file", report, p); + if (const char * p = std::getenv("LEDGER_INIT")) + process_option("init-file", report, p); + if (const char * p = std::getenv("PRICE_HIST")) + process_option("price-db", report, p); + if (const char * p = std::getenv("PRICE_EXP")) + process_option("price-exp", report, p); +#endif + + TRACE_FINISH(environment, 1); +} + +strings_list +read_command_line_arguments(report_t& report, int argc, char * argv[]) +{ + TRACE_START(arguments, 1, "Processed command-line arguments"); + + strings_list args; + process_arguments(argc - 1, argv + 1, report, args); + + if (args.empty()) { + help(std::cout); + throw int(1); + } + + TRACE_FINISH(arguments, 1); + + return args; +} + +void normalize_session_options(session_t& session) +{ + if (! session.cache_file) + session.use_cache = false; + + DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache); + + if (std::find(session.data_files.begin(), + session.data_files.end(), *session.cache_file) != + session.data_files.end()) + session.use_cache = false; + + DEBUG("ledger.session.cache", "2. use_cache = " << session.use_cache); + + INFO("Initialization file is " << session.init_file->string()); + INFO("Price database is " << session.price_db->string()); + INFO("Binary cache is " << session.cache_file->string()); + + foreach (const path& pathname, session.data_files) + INFO("Journal file is " << pathname.string()); + + if (! session.use_cache) + INFO("Binary cache mechanism will not be used"); +} + +function_t look_for_precommand(report_t& report, const string& verb) +{ + if (expr_t::ptr_op_t def = report.lookup(string("ledger_precmd_") + verb)) + return def->as_function(); + else + return function_t(); +} + +journal_t * read_journal_files(session_t& session) +{ + INFO_START(journal, "Read journal file"); + + journal_t * journal(session.create_journal()); + + std::size_t count = session.read_data(*journal, session.report->account); + if (count == 0) + throw_(parse_error, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); + + INFO_FINISH(journal); + + INFO("Found " << count << " entries"); + + TRACE_FINISH(entry_text, 1); + TRACE_FINISH(entry_details, 1); + TRACE_FINISH(entry_xacts, 1); + TRACE_FINISH(entries, 1); + TRACE_FINISH(session_parser, 1); + TRACE_FINISH(parsing_total, 1); + + return journal; +} + +function_t look_for_command(report_t& report, const string& verb) +{ + if (expr_t::ptr_op_t def = report.lookup(string("ledger_cmd_") + verb)) + return def->as_function(); + else + return function_t(); +} + +void normalize_report_options(report_t& report, const string& verb) +{ +#if 1 + // Patch up some of the reporting options based on what kind of + // command it was. + + // jww (2008-08-14): This code really needs to be rationalized away + // for 3.0. + + if (verb == "print" || verb == "entry" || verb == "dump") { + report.show_related = true; + report.show_all_related = true; + } + else if (verb == "equity") { + report.show_subtotal = true; + } + else if (report.show_related) { + if (verb[0] == 'r') { + report.show_inverted = true; + } else { + report.show_subtotal = true; + report.show_all_related = true; + } + } + + if (verb[0] != 'b' && verb[0] != 'r') + amount_t::keep_base = true; + + // Setup the default value for the display predicate + + if (report.display_predicate.empty()) { + if (verb[0] == 'b') { + if (! report.show_empty) + report.display_predicate = "total"; + if (! report.show_subtotal) { + if (! report.display_predicate.empty()) + report.display_predicate += "&"; + report.display_predicate += "depth<=1"; + } + } + else if (verb == "equity") { + report.display_predicate = "amount_expr"; // jww (2008-08-14): ??? + } + else if (verb[0] == 'r' && ! report.show_empty) { + report.display_predicate = "amount"; + } + } +#endif + + // Now setup the various formatting strings + + // jww (2008-08-14): I hear a song, and it's sound is "HaAaaCcK" + +#if 0 + if (! date_output_format.empty()) + date_t::output_format = date_output_format; +#endif + + amount_t::keep_price = report.keep_price; + amount_t::keep_date = report.keep_date; + amount_t::keep_tag = report.keep_tag; + + if (! report.report_period.empty() && ! report.sort_all) + report.entry_sort = true; +} + +void create_output_stream(report_t& report) +{ + report.output_stream.initialize(report.output_file, report.pager_path); +} + +void invoke_command_verb(report_t& report, + function_t& command, + string_iterator args_begin, + string_iterator args_end) +{ + // Create an argument scope containing the report command's + // arguments, and then invoke the command. + + call_scope_t command_args(report); + + for (string_iterator i = args_begin; i != args_end; i++) + command_args.push_back(string_value(*i)); + + INFO_START(command, "Finished executing command"); + + command(command_args); + + INFO_FINISH(command); +} + +void write_binary_cache(session_t& session, journal_t * journal) +{ + // Write out the binary cache, if need be + + if (session.use_cache && session.cache_dirty && session.cache_file) { + TRACE_START(binary_cache, 1, "Wrote binary journal file"); + + ofstream stream(*session.cache_file); +#if 0 + // jww (2009-01-31): NYI + journal->write(stream); +#endif + + TRACE_FINISH(binary_cache, 1); + } +} + +} // namespace ledger diff --git a/src/work.h b/src/work.h new file mode 100644 index 00000000..45915ab6 --- /dev/null +++ b/src/work.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2003-2009, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file work.h + * @author John Wiegley + * + * @brief Contains the top-level functions used by main.cc + */ +#ifndef _WORK_H +#define _WORK_H + +namespace ledger { + +typedef strings_list::iterator string_iterator; +typedef std::pair<string_iterator, string_iterator> string_iterator_pair; + +void handle_debug_options(int argc, char * argv[]); +void register_journal_parsers(session_t& session); +void read_environment_settings(report_t& report, char * envp[]); +strings_list read_command_line_arguments(report_t& report, + int argc, char * argv[]); +void normalize_session_options(session_t& session); +function_t look_for_precommand(report_t& report, const string& verb); +journal_t * read_journal_files(session_t& session); +function_t look_for_command(report_t& report, const string& verb); +void normalize_report_options(report_t& report, const string& verb); +void create_output_stream(report_t& report); +void invoke_command_verb(report_t& report, + function_t& command, + string_iterator args_begin, + string_iterator args_end); +void write_binary_cache(session_t& session, journal_t * journal); + +} // namespace ledger + +#endif // _WORK_H |