summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am3
-rw-r--r--src/account.cc4
-rw-r--r--src/item.cc2
-rw-r--r--src/ledger.h8
-rw-r--r--src/main.cc406
-rw-r--r--src/ofx.cc4
-rw-r--r--src/report.cc36
-rw-r--r--src/report.h2
-rw-r--r--src/session.cc77
-rw-r--r--src/session.h27
-rw-r--r--src/work.cc308
-rw-r--r--src/work.h65
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");
}
diff --git a/src/ofx.cc b/src/ofx.cc
index 4a54eaad..2e4a9d5d 100644
--- a/src/ofx.cc
+++ b/src/ofx.cc
@@ -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