diff options
author | John Wiegley <johnw@newartisans.com> | 2008-08-05 13:17:04 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-08-05 18:05:49 -0400 |
commit | f6f4a46cf5b14f9a2170cd6475958efbf320caec (patch) | |
tree | 05bc1defcdebc201de3dd10477483d906a842821 /src/main.cc | |
parent | b7970b29855563e4c67f85af8b31233eda80c22a (diff) | |
download | fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.gz fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.bz2 fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.zip |
Moved around most of the files so that source code is in src/, documentation
is in doc/, etc.
Diffstat (limited to 'src/main.cc')
-rw-r--r-- | src/main.cc | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..a5ca02d0 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2003-2008, 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 "session.h" +#include "report.h" +#include "option.h" +#include "output.h" +#include "help.h" + +#include "textual.h" +#include "qif.h" +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) +#include "xml.h" +#include "gnucash.h" +#endif +#ifdef HAVE_LIBOFX +#include "ofx.h" +#endif + +#ifdef HAVE_UNIX_PIPES +#include <sys/types.h> +#include <sys/wait.h> +#include "fdstream.h" +#endif + +namespace ledger { + string args_to_predicate(value_t::sequence_t::const_iterator begin, + value_t::sequence_t::const_iterator end) + { + string acct_value_expr; + string payee_value_expr; + string note_value_expr; + + string * value_expr; + + enum regexp_kind_t { + ACCOUNT_REGEXP, + PAYEE_REGEXP, + NOTE_REGEXP + } + kind = ACCOUNT_REGEXP; + + value_expr = &acct_value_expr; + + for ( ; begin != end; begin++) { + const string& arg((*begin).as_string()); + + if (arg == "--") { + kind = PAYEE_REGEXP; + value_expr = &payee_value_expr; + } + else if (arg == "/") { + kind = NOTE_REGEXP; + value_expr = ¬e_value_expr; + } + else { + if (! value_expr->empty()) + *value_expr += "|"; + + switch (kind) { + case ACCOUNT_REGEXP: + *value_expr += "account =~ "; + break; + case PAYEE_REGEXP: + *value_expr += "payee =~ "; + break; + case NOTE_REGEXP: + *value_expr += "note =~ "; + break; + } + + const char * p = arg.c_str(); + if (*p == '-') { + *value_expr += "!"; + p++; + } + + *value_expr += "/"; + if (kind == NOTE_REGEXP) *value_expr += ":"; + while (*p) { + if (*p == '/') + *value_expr += "\\"; + *value_expr += *p; + p++; + } + if (kind == NOTE_REGEXP) *value_expr += ":"; + *value_expr += "/"; + } + } + + string final_value_expr; + + if (! acct_value_expr.empty()) { + if (! payee_value_expr.empty() || + ! note_value_expr.empty()) + final_value_expr = string("(") + acct_value_expr + ")"; + else + final_value_expr = acct_value_expr; + } + + if (! payee_value_expr.empty()) { + if (! acct_value_expr.empty()) + final_value_expr += string("&(") + payee_value_expr + ")"; + else if (! note_value_expr.empty()) + final_value_expr = string("(") + payee_value_expr + ")"; + else + final_value_expr = payee_value_expr; + } + + if (! note_value_expr.empty()) { + if (! acct_value_expr.empty() || + ! payee_value_expr.empty()) + final_value_expr += string("&(") + note_value_expr + ")"; + else if (acct_value_expr.empty() && + payee_value_expr.empty()) + final_value_expr = note_value_expr; + } + + DEBUG("report.predicate", + "Regexp predicate expression = " << final_value_expr); + + return final_value_expr; + } + + template <class Formatter = format_xacts, + class handler_ptr = xact_handler_ptr, + void (report_t::*report_method)(handler_ptr) = + &report_t::xacts_report> + class reporter + { + string format_name; + + public: + reporter(const string& _format_name) + : format_name(_format_name) {} + + value_t operator()(call_scope_t& args) + { + report_t& report(find_scope<report_t>(args)); + var_t<string> format(args, format_name); + + if (! report.format_string.empty()) + *format = report.format_string; + + if (args.value().is_sequence() && + args.value().size() > 1) { + if (! report.predicate.empty()) + report.predicate = string("(") + report.predicate + ")&"; + report.predicate += + args_to_predicate(++args.value().as_sequence().begin(), + args.value().as_sequence().end()); + } + + (report.*report_method)(handler_ptr(new Formatter(report, *format))); + + return true; + } + }; + + int read_and_report(ledger::report_t& report, + int argc, char * argv[], char * envp[]) + { + using namespace ledger; + + session_t& session(report.session); + + // Handle the command-line arguments + + strings_list args; + process_arguments(argc - 1, argv + 1, false, 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 + session.use_cache = ! session.data_file.empty() && session.price_db; + + DEBUG("ledger.session.cache", "1. use_cache = " << session.use_cache); + + // Process the environment settings + + TRACE_START(environment, 1, "Processed environment variables"); + process_environment(const_cast<const char **>(envp), "LEDGER_", report); + TRACE_FINISH(environment, 1); + + optional<path> home; + if (const char * home_var = std::getenv("HOME")) + home = home_var; + + if (! session.init_file) + session.init_file = home ? *home / ".ledgerrc" : "./.ledgerrc"; + if (! session.price_db) + session.price_db = home ? *home / ".pricedb" : "./.pricedb"; + + if (! session.cache_file) + session.cache_file = home ? *home / ".ledger-cache" : "./.ledger-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"); + + // Configure the output stream + +#ifdef HAVE_UNIX_PIPES + int status, pfd[2]; // Pipe file descriptors +#endif + report.output_stream = &std::cout; + + if (report.output_file) { + report.output_stream = new ofstream(*report.output_file); + } +#ifdef HAVE_UNIX_PIPES + else if (report.pager) { + status = pipe(pfd); + if (status == -1) + throw_(std::logic_error, "Failed to create pipe"); + + status = fork(); + if (status < 0) { + throw_(std::logic_error, "Failed to fork child process"); + } + else if (status == 0) { // child + // Duplicate pipe's reading end into stdin + status = dup2(pfd[0], STDIN_FILENO); + if (status == -1) + perror("dup2"); + + // Close unuseful file descriptors: the pipe's writing and + // reading ends (the latter is not needed anymore, after the + // duplication). + close(pfd[1]); + close(pfd[0]); + + // Find command name: its the substring starting right of the + // rightmost '/' character in the pager pathname. See manpage + // for strrchr. + execlp(report.pager->native_file_string().c_str(), + basename(*report.pager).c_str(), NULL); + perror("execl"); + exit(1); + } + else { // parent + close(pfd[0]); + report.output_stream = new boost::fdostream(pfd[1]); + } + } +#endif + + // Read the command word and see if it's any of the debugging commands + // that Ledger supports. + + std::ostream& out(*report.output_stream); + + string verb = *arg++; + + if (verb == "parse") { + expr_t expr(*arg); + + out << "Value expression as input: " << *arg << std::endl; + + out << "Value expression as parsed: "; + expr.print(out, report); + out << std::endl; + + out << std::endl; + out << "--- Parsed tree ---" << std::endl; + expr.dump(out); + + out << std::endl; + out << "--- Calculated value ---" << std::endl; + expr.calc(report).print(out); + out << std::endl; + + return 0; + } + else if (verb == "compile") { + expr_t expr(*arg); + + out << "Value expression as input: " << *arg << std::endl; + out << "Value expression as parsed: "; + expr.print(out, report); + out << std::endl; + + out << std::endl; + out << "--- Parsed tree ---" << std::endl; + expr.dump(out); + + expr.compile(report); + + out << std::endl; + out << "--- Compiled tree ---" << std::endl; + expr.dump(out); + + out << std::endl; + out << "--- Calculated value ---" << std::endl; + expr.calc(report).print(out); + out << std::endl; + + return 0; + } + else if (verb == "eval") { + expr_t expr(*arg); + out << expr.calc(report).strip_annotations() << std::endl; + return 0; + } + else if (verb == "format") { + format_t fmt(*arg); + fmt.dump(out); + return 0; + } + else if (verb == "period") { + interval_t interval(*arg); + + if (! is_valid(interval.begin)) { + out << "Time period has no beginning." << std::endl; + } else { + out << "begin: " << format_date(interval.begin) << std::endl; + out << " end: " << format_date(interval.end) << std::endl; + out << std::endl; + + date_t date = interval.first(); + + for (int i = 0; i < 20; i++) { + out << std::right; + out.width(2); + + out << i << ": " << format_date(date) << std::endl; + + date = interval.increment(date); + if (is_valid(interval.end) && date >= interval.end) + break; + } + } + return 0; + } + + // Parse the initialization file, which can only be textual; then + // parse the journal data. + + session.read_init(); + + 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_date, 1); + TRACE_FINISH(entry_details, 1); + TRACE_FINISH(entry_xacts, 1); + TRACE_FINISH(entries, 1); + TRACE_FINISH(parsing_total, 1); + + // Create a command object based on the command verb that was seen + // above. + + function_t command; + + if (verb == "register" || verb == "reg" || verb == "r") + command = reporter<>("register_format"); + else if (verb == "print" || verb == "p") + command = reporter<>("print_format"); + else if (verb == "balance" || verb == "bal" || verb == "b") + command = reporter<format_accounts, acct_handler_ptr, + &report_t::accounts_report>("balance_format"); + else if (verb == "equity") + command = reporter<format_equity, acct_handler_ptr, + &report_t::accounts_report>("print_format"); +#if 0 + else if (verb == "entry") + command = entry_command(); + else if (verb == "dump") + command = dump_command(); + else if (verb == "output") + command = output_command(); + else if (verb == "prices") + command = prices_command(); + else if (verb == "pricesdb") + command = pricesdb_command(); + else if (verb == "csv") + command = csv_command(); + else if (verb == "emacs" || verb == "lisp") + command = emacs_command(); + else if (verb == "xml") + command = xml_command(); +#endif + else { + char buf[128]; + std::strcpy(buf, "cmd_"); + std::strcat(buf, verb.c_str()); + + if (expr_t::ptr_op_t def = report.lookup(buf)) + command = def->as_function(); + + if (! command) + throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); + } + + // 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 + + // If the user specified a pager, wait for it to exit now + +#ifdef HAVE_UNIX_PIPES + if (! report.output_file && report.pager) { + checked_delete(report.output_stream); + close(pfd[1]); + + // Wait for child to finish + wait(&status); + if (status & 0xffff != 0) + throw_(std::logic_error, "Something went wrong in the pager"); + } +#endif + else if (DO_VERIFY() && report.output_file) { + checked_delete(report.output_stream); + } + + 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"); + + std::auto_ptr<ledger::session_t> session(new ledger::session_t); + + ledger::set_session_context(session.get()); + +#if 0 + session->register_parser(new ledger::journal_t::binary_parser_t); +#endif +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + session->register_parser(new ledger::xml_parser_t); + session->register_parser(new ledger::gnucash_parser_t); +#endif +#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()) + ledger::set_session_context(); + else + session.release(); // don't free anything! just let it leak + } + catch (const std::exception& err) { + std::cout.flush(); + std::cerr << "Error: " << ledger::error_context() << err.what() + << std::endl; + } + catch (int _status) { + status = _status; + } + + IF_VERIFY() { + INFO("Ledger ended (Boost/libstdc++ may still hold memory)"); + ledger::shutdown_memory_tracing(); + } else { + INFO("Ledger ended"); + } + + return status; +} + +// main.cc ends here. |