summaryrefslogtreecommitdiff
path: root/src/main.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-01-31 18:52:34 -0400
committerJohn Wiegley <johnw@newartisans.com>2009-01-31 18:52:34 -0400
commit9d267fa1331a570e2b4c978f0b35a107a47b51c1 (patch)
treeed8c5bc7d5b3b2024fb4fee80f4ec86332fb277f /src/main.cc
parent75daee6a5df8154ecd6011f93af746105529a13d (diff)
downloadfork-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.
Diffstat (limited to 'src/main.cc')
-rw-r--r--src/main.cc406
1 files changed, 100 insertions, 306 deletions
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");
}