From 7380da43ab403dacb41d2010093d11942bb7cec1 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 21 May 2007 20:42:05 +0000 Subject: Many changes. --- src/driver/fdstream.hpp | 215 ++++++++++++++++++++++ src/driver/main.cc | 481 ++++++++++++++++++++++++++++++++++++++++++++++++ src/driver/option.cc | 222 ++++++++++++++++++++++ src/driver/option.h | 53 ++++++ src/driver/report.cc | 223 ++++++++++++++++++++++ src/driver/report.h | 190 +++++++++++++++++++ src/driver/session.cc | 313 +++++++++++++++++++++++++++++++ src/driver/session.h | 196 ++++++++++++++++++++ 8 files changed, 1893 insertions(+) create mode 100644 src/driver/fdstream.hpp create mode 100644 src/driver/main.cc create mode 100644 src/driver/option.cc create mode 100644 src/driver/option.h create mode 100644 src/driver/report.cc create mode 100644 src/driver/report.h create mode 100644 src/driver/session.cc create mode 100644 src/driver/session.h (limited to 'src/driver') diff --git a/src/driver/fdstream.hpp b/src/driver/fdstream.hpp new file mode 100644 index 00000000..ffcf5709 --- /dev/null +++ b/src/driver/fdstream.hpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2003-2007, 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. + */ + +/* The following code declares classes to read from and write to + * file descriptore or file handles. + * + * See + * http://www.josuttis.com/cppcode + * for details and the latest version. + * + * - open: + * - integrating BUFSIZ on some systems? + * - optimized reading of multiple characters + * - stream for reading AND writing + * - i18n + * + * (C) Copyright Nicolai M. Josuttis 2001. + * Permission to copy, use, modify, sell and distribute this software + * is granted provided this copyright notice appears in all copies. + * This software is provided "as is" without express or implied + * warranty, and with no claim as to its suitability for any purpose. + * + * Version: Jul 28, 2002 + * History: + * Jul 28, 2002: bugfix memcpy() => memmove() + * fdinbuf::underflow(): cast for return statements + * Aug 05, 2001: first public version + */ +#ifndef BOOST_FDSTREAM_HPP +#define BOOST_FDSTREAM_HPP + +#include +#include +#include +// for EOF: +#include +// for memmove(): +#include + + +// low-level read and write functions +#ifdef _MSC_VER +# include +#else +# include +//extern "C" { +// int write (int fd, const char* buf, int num); +// int read (int fd, char* buf, int num); +//} +#endif + + +// BEGIN namespace BOOST +namespace boost { + + +/************************************************************ + * fdostream + * - a stream that writes on a file descriptor + ************************************************************/ + + +class fdoutbuf : public std::streambuf { + protected: + int fd; // file descriptor + public: + // constructor + fdoutbuf (int _fd) : fd(_fd) { + } + protected: + // write one character + virtual int_type overflow (int_type c) { + if (c != EOF) { + char z = c; + if (write (fd, &z, 1) != 1) { + return EOF; + } + } + return c; + } + // write multiple characters + virtual + std::streamsize xsputn (const char* s, + std::streamsize num) { + return write(fd,s,num); + } +}; + +class fdostream : public std::ostream { + protected: + fdoutbuf buf; + public: + fdostream (int fd) : std::ostream(0), buf(fd) { + rdbuf(&buf); + } +}; + + +/************************************************************ + * fdistream + * - a stream that reads on a file descriptor + ************************************************************/ + +class fdinbuf : public std::streambuf { + protected: + int fd; // file descriptor + protected: + /* data buffer: + * - at most, pbSize characters in putback area plus + * - at most, bufSize characters in ordinary read buffer + */ + static const int pbSize = 4; // size of putback area + static const int bufSize = 1024; // size of the data buffer + char buffer[bufSize+pbSize]; // data buffer + + public: + /* constructor + * - initialize file descriptor + * - initialize empty data buffer + * - no putback area + * => force underflow() + */ + fdinbuf (int _fd) : fd(_fd) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + protected: + // insert new characters into the buffer + virtual int_type underflow () { +#ifndef _MSC_VER + using std::memmove; +#endif + + // is read position before end of buffer? + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + /* process size of putback area + * - use number of characters read + * - but at most size of putback area + */ + int numPutback; + numPutback = gptr() - eback(); + if (numPutback > pbSize) { + numPutback = pbSize; + } + + /* copy up to pbSize characters previously read into + * the putback area + */ + memmove (buffer+(pbSize-numPutback), gptr()-numPutback, + numPutback); + + // read at most bufSize new characters + int num; + num = read (fd, buffer+pbSize, bufSize); + if (num <= 0) { + // ERROR or EOF + return EOF; + } + + // reset buffer pointers + setg (buffer+(pbSize-numPutback), // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize+num); // end of buffer + + // return next character + return traits_type::to_int_type(*gptr()); + } +}; + +class fdistream : public std::istream { + protected: + fdinbuf buf; + public: + fdistream (int fd) : std::istream(0), buf(fd) { + rdbuf(&buf); + } +}; + + +} // END namespace boost + +#endif /*BOOST_FDSTREAM_HPP*/ diff --git a/src/driver/main.cc b/src/driver/main.cc new file mode 100644 index 00000000..469bb5ee --- /dev/null +++ b/src/driver/main.cc @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2003-2007, 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 "utils.h" +#include "option.h" +//#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) +//#include "gnucash.h" +//#endif +//#include "qif.h" +//#include "ofx.h" +#include "jbuilder.h" +#include "compile.h" + +#include + +#ifdef HAVE_UNIX_PIPES +#include +#include +#include +#endif + +static 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()) { +#if 0 + help(std::cerr); +#endif + 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(envp), "LEDGER_", report); + TRACE_FINISH(environment, 1); + + optional 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"); + + // Read the command word and create a command object based on it + + string verb = *arg++; + + xml::xpath_t::function_t command; + +#if 0 + if (verb == "register" || verb == "reg" || verb == "r") + command = register_command(); + else if (verb == "balance" || verb == "bal" || verb == "b") + command = balance_command(); + else if (verb == "print" || verb == "p") + command = print_command(); + else if (verb == "equity") + command = equity_command(); + 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 +#endif + if (verb == "xml") + command = bind(xml_command, _1); + else if (verb == "expr") + ; + else if (verb == "xpath") + ; + else if (verb == "parse") { + xml::xpath_t expr(*arg); + xml::document_t temp(xml::LEDGER_NODE); + + xml::xpath_t::context_scope_t doc_scope(report, &temp); + + IF_INFO() { + std::cout << "Value expression tree:" << std::endl; + expr.dump(std::cout); + std::cout << std::endl; + + std::cout << "Value expression parsed was:" << std::endl; + expr.print(std::cout, doc_scope); + std::cout << std::endl << std::endl; + + expr.compile(doc_scope); + + std::cout << "Value expression after compiling:" << std::endl; + expr.dump(std::cout); + std::cout << std::endl; + + std::cout << "Value expression is now:" << std::endl; + expr.print(std::cout, doc_scope); + std::cout << std::endl << std::endl; + + std::cout << "Result of calculation: "; + } + + std::cout << expr.calc(doc_scope).strip_annotations() << std::endl; + + return 0; + } + else { + char buf[128]; + std::strcpy(buf, "command_"); + std::strcat(buf, verb.c_str()); + + if (xml::xpath_t::ptr_op_t def = report.lookup(buf)) + command = def->as_function(); + + if (! command) + throw_(std::logic_error, string("Unrecognized command '") + verb + "'"); + } + + // Parse the initialization file, which can only be textual; then + // parse the journal data. + + session.read_init(); + + INFO_START(journal, "Read journal file"); + + xml::document_t xml_document(xml::LEDGER_NODE); + journal_t * journal = session.create_journal(); + xml::journal_builder_t builder(xml_document, journal); + + if (! session.read_data(builder, journal, report.account)) + throw_(parse_error, "Failed to locate any journal entries; " + "did you specify a valid file with -f?"); + + INFO_FINISH(journal); + + 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); + + // Configure the output stream + +#ifdef HAVE_UNIX_PIPES + int status, pfd[2]; // Pipe file descriptors +#endif + std::ostream * out = &std::cout; + + if (report.output_file) { + out = 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(), (char *)0); + perror("execl"); + exit(1); + } + else { // parent + close(pfd[0]); + out = new boost::fdostream(pfd[1]); + } + } +#endif + + report.define("ostream", value_t(out)); + + // Are we handling the expr commands? Do so now. + + xml::xpath_t::context_scope_t doc_scope(report, &xml_document); + + if (verb == "expr") { + xml::xpath_t expr(*arg); + + IF_INFO() { + *out << "Value expression tree:" << std::endl; + expr.dump(*out); + *out << std::endl; + *out << "Value expression parsed was:" << std::endl; + expr.print(*out, doc_scope); + *out << std::endl << std::endl; + *out << "Result of calculation: "; + } + + *out << expr.calc(doc_scope).strip_annotations() << std::endl; + + return 0; + } + else if (verb == "xpath") { + std::cout << "XPath parsed: "; + + xml::xpath_t xpath(*arg); + xpath.print(*out, doc_scope); + *out << std::endl; + + IF_INFO() { + *out << "Raw results:" << std::endl; + + foreach (const value_t& value, xpath.find_all(doc_scope)) { + if (value.is_xml_node()) + value.as_xml_node()->print(std::cout); + else + std::cout << value; + std::cout << std::endl; + } + + *out << "Compiled results:" << std::endl; + } + + xml::compile_node(xml_document, report); + + foreach (const value_t& value, xpath.find_all(doc_scope)) { + if (value.is_xml_node()) + value.as_xml_node()->print(std::cout); + else + std::cout << value; + std::cout << std::endl; + } + return 0; + } + + // Apply transforms to the hierarchical document structure + + INFO_START(transforms, "Applied transforms"); + report.apply_transforms(doc_scope); + INFO_FINISH(transforms); + + // Create an argument scope containing the report command's + // arguments, and then invoke the command. + + xml::xpath_t::call_scope_t command_args(doc_scope); + + for (strings_list::iterator i = arg; i != args.end(); i++) + command_args.push_back(value_t(*i, true)); + + INFO_START(command, "Did user command '" << verb << "'"); + + command(command_args); + + INFO_FINISH(command); + + // 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"); + +#if 0 + ofstream stream(*session.cache_file); + write_binary_journal(stream, journal); +#endif + + TRACE_FINISH(binary_cache, 1); + } + + // If the user specified a pager, wait for it to exit now + +#ifdef HAVE_UNIX_PIPES + if (! report.output_file && report.pager) { + checked_delete(out); + 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(out); + } + + 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; + ledger::_trace_level = boost::lexical_cast(argv[i + 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 session(new ledger::session_t); + + ledger::set_session_context(session.get()); + +#if 0 + session->register_parser(new binary_parser_t); +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + session->register_parser(new xml::xml_parser_t); + session->register_parser(new gnucash_parser_t); +#endif +#ifdef HAVE_LIBOFX + session->register_parser(new ofx_parser_t); +#endif + session->register_parser(new qif_parser_t); +#endif + session->register_parser(new ledger::textual_parser_t); + + std::auto_ptr report(new ledger::report_t(*session.get())); + + status = read_and_report(*report.get(), argc, argv, envp); + + if (DO_VERIFY()) { + ledger::set_session_context(); + } else { + report.release(); + session.release(); + } + } +#if 0 + catch (error * err) { + std::cout.flush(); + // Push a null here since there's no file context + if (err->context.empty() || + ! dynamic_cast(err->context.front())) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Error"); + std::cerr << err->what() << std::endl; + checked_delete(err); + } + catch (fatal * err) { + std::cout.flush(); + // Push a null here since there's no file context + if (err->context.empty() || + ! dynamic_cast(err->context.front())) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Fatal"); + std::cerr << err->what() << std::endl; + checked_delete(err); + } +#endif + catch (const std::exception& err) { + std::cout.flush(); + std::cerr << "Error: " << 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. diff --git a/src/driver/option.cc b/src/driver/option.cc new file mode 100644 index 00000000..0b7f7ef9 --- /dev/null +++ b/src/driver/option.cc @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2003-2007, 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 "option.h" + +namespace ledger { + +namespace { + typedef tuple op_bool_tuple; + + op_bool_tuple find_option(xml::xpath_t::scope_t& scope, const string& name) + { + char buf[128]; + std::strcpy(buf, "option_"); + char * p = &buf[7]; + for (const char * q = name.c_str(); *q; q++) { + if (*q == '-') + *p++ = '_'; + else + *p++ = *q; + } + *p = '\0'; + + xml::xpath_t::ptr_op_t op = scope.lookup(buf); + if (op) + return op_bool_tuple(op, false); + + *p++ = '_'; + *p = '\0'; + + return op_bool_tuple(scope.lookup(buf), true); + } + + op_bool_tuple find_option(xml::xpath_t::scope_t& scope, const char letter) + { + char buf[10]; + std::strcpy(buf, "option_"); + buf[7] = letter; + buf[8] = '\0'; + + xml::xpath_t::ptr_op_t op = scope.lookup(buf); + if (op) + return op_bool_tuple(op, false); + + buf[8] = '_'; + buf[9] = '\0'; + + return op_bool_tuple(scope.lookup(buf), true); + } + + void process_option(const xml::xpath_t::function_t& opt, + xml::xpath_t::scope_t& scope, const char * arg) + { +#if 0 + try { +#endif + xml::xpath_t::call_scope_t args(scope); + if (arg) + args.push_back(value_t(arg, true)); + + opt(args); +#if 0 + } + catch (error * err) { + err->context.push_back + (new error_context + (string("While parsing option '--") + opt->long_opt + + "'" + (opt->short_opt != '\0' ? + (string(" (-") + opt->short_opt + "):") : ":"))); + throw err; + } +#endif + } +} + +void process_option(const string& name, xml::xpath_t::scope_t& scope, + const char * arg) +{ + op_bool_tuple opt(find_option(scope, name)); + if (opt.get<0>()) + process_option(opt.get<0>()->as_function(), scope, arg); +} + +void process_environment(const char ** envp, const string& tag, + xml::xpath_t::scope_t& scope) +{ + const char * tag_p = tag.c_str(); + unsigned int tag_len = tag.length(); + + for (const char ** p = envp; *p; p++) + if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) { + char buf[128]; + char * r = buf; + const char * q; + for (q = *p + tag_len; + *q && *q != '=' && r - buf < 128; + q++) + if (*q == '_') + *r++ = '-'; + else + *r++ = std::tolower(*q); + *r = '\0'; + + if (*q == '=') { +#if 0 + try { +#endif + process_option(string(buf), scope, q + 1); +#if 0 + } + catch (error * err) { + err->context.push_back + (new error_context + (string("While parsing environment variable option '") + + *p + "':")); + throw err; + } +#endif + } + } +} + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t& scope, + std::list& args) +{ + for (char ** i = argv; *i; i++) { + if ((*i)[0] != '-') { + if (anywhere) { + args.push_back(*i); + continue; + } else { + for (; *i; i++) + args.push_back(*i); + break; + } + } + + // --long-option or -s + if ((*i)[1] == '-') { + if ((*i)[2] == '\0') + break; + + char * name = *i + 2; + char * value = NULL; + if (char * p = std::strchr(name, '=')) { + *p++ = '\0'; + value = p; + } + + op_bool_tuple opt(find_option(scope, name)); + if (! opt.get<0>()) + throw_(option_error, "illegal option --" << name); + + if (opt.get<1>() && value == NULL) { + value = *++i; + if (value == NULL) + throw_(option_error, "missing option argument for --" << name); + } + process_option(opt.get<0>()->as_function(), scope, value); + } + else if ((*i)[1] == '\0') { + throw_(option_error, "illegal option -"); + } + else { + typedef tuple op_bool_char_tuple; + + std::list option_queue; + + int x = 1; + for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { + op_bool_tuple opt(find_option(scope, c)); + if (! opt.get<0>()) + throw_(option_error, "illegal option -" << c); + + option_queue.push_back + (op_bool_char_tuple(opt.get<0>(), opt.get<1>(), c)); + } + + foreach (op_bool_char_tuple& o, option_queue) { + char * value = NULL; + if (o.get<1>()) { + value = *++i; + if (value == NULL) + throw_(option_error, + "missing option argument for -" << o.get<2>()); + } + process_option(o.get<0>()->as_function(), scope, value); + } + } + } +} + +} // namespace ledger diff --git a/src/driver/option.h b/src/driver/option.h new file mode 100644 index 00000000..d26c8417 --- /dev/null +++ b/src/driver/option.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2003-2007, 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. + */ + +#ifndef _OPTION_H +#define _OPTION_H + +#include "xpath.h" + +namespace ledger { + +void process_option(const string& name, xml::xpath_t::scope_t& scope, + const char * arg = NULL); + +void process_environment(const char ** envp, const string& tag, + xml::xpath_t::scope_t& scope); + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t& scope, + std::list& args); + +DECLARE_EXCEPTION(option_error); + +} // namespace ledger + +#endif // _OPTION_H diff --git a/src/driver/report.cc b/src/driver/report.cc new file mode 100644 index 00000000..ee3382da --- /dev/null +++ b/src/driver/report.cc @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2003-2007, 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 "report.h" + +namespace ledger { + +report_t::~report_t() +{ + TRACE_DTOR(report_t); +} + +void report_t::apply_transforms(xml::xpath_t::scope_t& scope) +{ + typedef tuple, value_t> transform_details_tuple; + + foreach (transform_details_tuple& transform_details, transforms) { + xml::xpath_t::call_scope_t call_args(scope); + call_args.set_args(transform_details.get<1>()); + (*transform_details.get<0>())(call_args); + } +} + +value_t report_t::abbrev(xml::xpath_t::call_scope_t& args) +{ + if (args.size() < 2) + throw_(std::logic_error, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); + + string str = args[0].as_string(); + long wid = args[1]; + + elision_style_t style = session.elision_style; + if (args.size() == 3) + style = (elision_style_t)args[2].as_long(); + + long abbrev_len = session.abbrev_length; + if (args.size() == 4) + abbrev_len = args[3].as_long(); + + return value_t(abbreviate(str, wid, style, true, (int)abbrev_len), true); +} + +value_t report_t::ftime(xml::xpath_t::call_scope_t& args) +{ + if (args.size() < 1) + throw_(std::logic_error, "usage: ftime(DATE [, DATE_FORMAT])"); + + moment_t date = args[0].as_datetime(); + + string date_format; + if (args.size() == 2) + date_format = args[1].as_string(); +#if 0 + // jww (2007-04-18): Need to setup an output facet here + else + date_format = moment_t::output_format; + + return value_t(date.as_string(date_format), true); +#else + return NULL_VALUE; +#endif +} + +#if 0 +optional +report_t::resolve(const string& name, xml::xpath_t::call_scope_t& args) +{ + const char * p = name.c_str(); + switch (*p) { + case 'a': + if (name == "abbrev") { + return abbrev(args); + } + break; + + case 'f': + if (name == "ftime") { + return ftime(args); + } + break; + } + return xml::xpath_t::scope_t::resolve(name, args); +} +#endif + +xml::xpath_t::ptr_op_t report_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'a': +#if 0 + if (std::strcmp(p, "accounts") == 0) + return MAKE_FUNCTOR(report_t::option_accounts); + else +#endif + if (std::strcmp(p, "amount") == 0) + return MAKE_FUNCTOR(report_t::option_amount); + break; + + case 'b': + if (std::strcmp(p, "bar") == 0) + return MAKE_FUNCTOR(report_t::option_bar); + break; + +#if 0 + case 'c': + if (std::strcmp(p, "clean") == 0) + return MAKE_FUNCTOR(report_t::option_clean); + else if (std::strcmp(p, "compact") == 0) + return MAKE_FUNCTOR(report_t::option_compact); + break; +#endif + + case 'e': +#if 0 + if (std::strcmp(p, "entries") == 0) + return MAKE_FUNCTOR(report_t::option_entries); + else if (std::strcmp(p, "eval") == 0) + return MAKE_FUNCTOR(report_t::option_eval); + else if (std::strcmp(p, "exclude") == 0) + return MAKE_FUNCTOR(report_t::option_remove); +#endif + break; + + case 'f': +#if 0 + if (std::strcmp(p, "foo") == 0) + return MAKE_FUNCTOR(report_t::option_foo); + else +#endif + if (std::strcmp(p, "format") == 0) + return MAKE_FUNCTOR(report_t::option_format); + break; + + case 'i': +#if 0 + if (std::strcmp(p, "include") == 0) + return MAKE_FUNCTOR(report_t::option_select); +#endif + break; + + case 'l': +#if 0 + if (! *(p + 1) || std::strcmp(p, "limit") == 0) + return MAKE_FUNCTOR(report_t::option_limit); +#endif + break; + +#if 0 + case 'm': + if (std::strcmp(p, "merge") == 0) + return MAKE_FUNCTOR(report_t::option_merge); + break; +#endif + + case 'r': +#if 0 + if (std::strcmp(p, "remove") == 0) + return MAKE_FUNCTOR(report_t::option_remove); +#endif + break; + +#if 0 + case 's': + if (std::strcmp(p, "select") == 0) + return MAKE_FUNCTOR(report_t::option_select); + else if (std::strcmp(p, "split") == 0) + return MAKE_FUNCTOR(report_t::option_split); + break; +#endif + + case 't': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t::option_amount); + else if (std::strcmp(p, "total") == 0) + return MAKE_FUNCTOR(report_t::option_total); + break; + + case 'T': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t::option_total); + break; + } + } + break; + } + + return xml::xpath_t::symbol_scope_t::lookup(name); +} + +} // namespace ledger diff --git a/src/driver/report.h b/src/driver/report.h new file mode 100644 index 00000000..b435341b --- /dev/null +++ b/src/driver/report.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2003-2007, 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. + */ + +#ifndef _REPORT_H +#define _REPORT_H + +#include "session.h" +#include "transform.h" + +namespace ledger { + +typedef std::list strings_list; + +class report_t : public xml::xpath_t::symbol_scope_t +{ +public: + optional output_file; + string format_string; + string amount_expr; + string total_expr; + string date_output_format; + + unsigned long budget_flags; + + string account; + optional pager; + + bool show_totals; + bool raw_mode; + + session_t& session; + transform_t * last_transform; + + std::list, value_t> > transforms; + + explicit report_t(session_t& _session) + : xml::xpath_t::symbol_scope_t(downcast(_session)), + show_totals(false), + raw_mode(false), + session(_session), + last_transform(NULL) + { + TRACE_CTOR(report_t, "session_t&"); +#if 0 + eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); +#endif + } + + virtual ~report_t(); + + void apply_transforms(xml::xpath_t::scope_t& scope); + + // + // Utility functions for value expressions + // + + value_t ftime(xml::xpath_t::call_scope_t& args); + value_t abbrev(xml::xpath_t::call_scope_t& args); + + // + // Config options + // + + void eval(const string& expr) { +#if 0 + xml::xpath_t(expr).compile((xml::document_t *)NULL, this); +#endif + } + value_t option_eval(xml::xpath_t::call_scope_t& args) { + eval(args[0].as_string()); + return NULL_VALUE; + } + + value_t option_amount(xml::xpath_t::call_scope_t& args) { + eval(string("t=") + args[0].as_string()); + return NULL_VALUE; + } + value_t option_total(xml::xpath_t::call_scope_t& args) { + eval(string("T()=") + args[0].as_string()); + return NULL_VALUE; + } + + value_t option_format(xml::xpath_t::call_scope_t& args) { + format_string = args[0].as_string(); + return NULL_VALUE; + } + + value_t option_raw(xml::xpath_t::call_scope_t& args) { + raw_mode = true; + return NULL_VALUE; + } + + value_t option_foo(xml::xpath_t::call_scope_t& args) { + std::cout << "This is foo" << std::endl; + return NULL_VALUE; + } + value_t option_bar(xml::xpath_t::call_scope_t& args) { + std::cout << "This is bar: " << args[0] << std::endl; + return NULL_VALUE; + } + + // + // Transform options + // + +#if 0 + value_t option_select(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new select_transform(args[0].as_string())); + return NULL_VALUE; + } + value_t option_limit(xml::xpath_t::call_scope_t& args) { + string expr = (string("//xact[") + + args[0].as_string() + "]"); + transforms.push_back(new select_transform(expr)); + return NULL_VALUE; + } + + value_t option_remove(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new remove_transform(args[0].as_string())); + return NULL_VALUE; + } + + value_t option_accounts(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new accounts_transform); + return NULL_VALUE; + } + value_t option_compact(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new compact_transform); + return NULL_VALUE; + } + value_t option_clean(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new clean_transform); + return NULL_VALUE; + } + value_t option_entries(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new entries_transform); + return NULL_VALUE; + } + + value_t option_split(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new split_transform); + return NULL_VALUE; + } + value_t option_merge(xml::xpath_t::call_scope_t& args) { + transforms.push_back(new merge_transform); + return NULL_VALUE; + } +#endif + + // + // Scope members + // + + virtual xml::xpath_t::ptr_op_t lookup(const string& name); +}; + +string abbrev(const string& str, unsigned int width, + const bool is_account); + +} // namespace ledger + +#endif // _REPORT_H diff --git a/src/driver/session.cc b/src/driver/session.cc new file mode 100644 index 00000000..71f5f349 --- /dev/null +++ b/src/driver/session.cc @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2003-2007, 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" + +namespace ledger { + +session_t * session_t::current = NULL; + +#if 0 +boost::mutex session_t::session_mutex; +#endif + +static void initialize(); +static void shutdown(); + +void set_session_context(session_t * session) +{ +#if 0 + session_t::session_mutex.lock(); +#endif + + if (session && ! session_t::current) { + initialize(); + } + else if (! session && session_t::current) { + shutdown(); +#if 0 + session_t::session_mutex.unlock(); +#endif + } + + session_t::current = session; +} + +void release_session_context() +{ +#if 0 + session_t::session_mutex.unlock(); +#endif +} + +session_t::session_t() + : symbol_scope_t(), + + register_format + ("%((//entry)%{date} %-.20{payee}" + "%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"), + wide_register_format + ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" + "%48|%-.38A %22.108t %!22.132T\n"), + print_format +#if 1 + ("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"), +#else + ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), +#endif + balance_format + ("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"), + equity_format + + ("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\n)"), + plot_amount_format + ("%D %(@S(@t))\n"), + plot_total_format + ("%D %(@S(@T))\n"), + write_hdr_format + ("%d %Y%C%P\n"), + write_xact_format + (" %-34W %12o%n\n"), + prices_format + ("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"), + pricesdb_format + ("P %[%Y/%m/%d %H:%M:%S] %A %t\n"), + + pricing_leeway(24 * 3600), + + download_quotes(false), + use_cache(false), + cache_dirty(false), + + now(now), + + elision_style(ABBREVIATE), + abbrev_length(2), + + ansi_codes(false), + ansi_invert(false) +{ + TRACE_CTOR(session_t, "xml::xpath_t::scope_t&"); +} + +std::size_t session_t::read_journal(std::istream& in, + const path& pathname, + xml::builder_t& builder) +{ +#if 0 + if (! master) + master = journal->master; +#endif + + foreach (parser_t& parser, parsers) + if (parser.test(in)) + return parser.parse(in, pathname, builder); + + return 0; +} + +std::size_t session_t::read_journal(const path& pathname, + xml::builder_t& builder) +{ +#if 0 + journal->sources.push_back(pathname); +#endif + + if (! exists(pathname)) + throw_(std::logic_error, "Cannot read file" << pathname); + + ifstream stream(pathname); + return read_journal(stream, pathname, builder); +} + +void session_t::read_init() +{ + if (! init_file) + return; + + if (! exists(*init_file)) + throw_(std::logic_error, "Cannot read init file" << *init_file); + + ifstream init(*init_file); + + // jww (2006-09-15): Read initialization options here! +} + +std::size_t session_t::read_data(xml::builder_t& builder, + journal_t * journal, + const string& master_account) +{ + if (data_file.empty()) + throw_(parse_error, "No journal file was specified (please use -f)"); + + TRACE_START(parser, 1, "Parsing journal file"); + + std::size_t entry_count = 0; + + DEBUG("ledger.cache", "3. use_cache = " << use_cache); + + if (use_cache && cache_file) { + DEBUG("ledger.cache", "using_cache " << cache_file->string()); + cache_dirty = true; + if (exists(*cache_file)) { + scoped_variable > + save_price_db(journal->price_db, price_db); + + entry_count += read_journal(*cache_file, builder); + if (entry_count > 0) + cache_dirty = false; + } + } + + if (entry_count == 0) { + account_t * acct = NULL; + if (! master_account.empty()) + acct = journal->find_account(master_account); + + journal->price_db = price_db; + if (journal->price_db && exists(*journal->price_db)) { + if (read_journal(*journal->price_db, builder)) { + throw_(parse_error, "Entries not allowed in price history file"); + } else { + DEBUG("ledger.cache", + "read price database " << journal->price_db->string()); + journal->sources.pop_back(); + } + } + + DEBUG("ledger.cache", "rejected cache, parsing " << data_file.string()); + if (data_file == "-") { + use_cache = false; + journal->sources.push_back(""); + entry_count += read_journal(std::cin, "", builder); + } + else if (exists(data_file)) { + entry_count += read_journal(data_file, builder); + if (journal->price_db) + journal->sources.push_back(*journal->price_db); + } + } + + VERIFY(journal->valid()); + + TRACE_STOP(parser, 1); + + return entry_count; +} + +#if 0 +optional +session_t::resolve(const string& name, xml::xpath_t::scope_t& locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'd': +#if 0 + if (name == "date_format") { + // jww (2007-04-18): What to do here? + return value_t(moment_t::output_format, true); + } +#endif + break; + + case 'n': + switch (*++p) { + case 'o': + if (name == "now") + return value_t(now); + break; + } + break; + + case 'r': + if (name == "register_format") + return value_t(register_format, true); + break; + } + return xml::xpath_t::scope_t::resolve(name, locals); +} +#endif + +xml::xpath_t::ptr_op_t session_t::lookup(const string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'd': + if (std::strcmp(p, "debug_") == 0) + return MAKE_FUNCTOR(session_t::option_debug_); + break; + + case 'f': + if ((*(p + 1) == '_' && ! *(p + 2)) || + std::strcmp(p, "file_") == 0) + return MAKE_FUNCTOR(session_t::option_file_); + break; + + case 't': + if (std::strcmp(p, "trace_") == 0) + return MAKE_FUNCTOR(session_t::option_trace_); + break; + + case 'v': + if (! *(p + 1) || std::strcmp(p, "verbose") == 0) + return MAKE_FUNCTOR(session_t::option_verbose); + else if (std::strcmp(p, "verify") == 0) + return MAKE_FUNCTOR(session_t::option_verify); + break; + } + } + break; + } + + return xml::xpath_t::symbol_scope_t::lookup(name); +} + +// jww (2007-04-26): All of Ledger should be accessed through a +// session_t object +static void initialize() +{ + amount_t::initialize(); + value_t::initialize(); + xml::xpath_t::initialize(); +} + +static void shutdown() +{ + xml::xpath_t::shutdown(); + value_t::shutdown(); + amount_t::shutdown(); +} + +} // namespace ledger diff --git a/src/driver/session.h b/src/driver/session.h new file mode 100644 index 00000000..43d7d722 --- /dev/null +++ b/src/driver/session.h @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2003-2007, 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. + */ + +#ifndef _SESSION_H +#define _SESSION_H + +#include "xpath.h" +#include "journal.h" +#include "parser.h" +#include "abbrev.h" + +namespace ledger { + +class session_t : public xml::xpath_t::symbol_scope_t +{ + public: + static session_t * current; + + path data_file; + optional init_file; + optional cache_file; + optional price_db; + + string register_format; + string wide_register_format; + string print_format; + string balance_format; + string equity_format; + string plot_amount_format; + string plot_total_format; + string write_hdr_format; + string write_xact_format; + string prices_format; + string pricesdb_format; + + unsigned long pricing_leeway; + + bool download_quotes; + bool use_cache; + bool cache_dirty; + + moment_t now; + + elision_style_t elision_style; + + int abbrev_length; + + bool ansi_codes; + bool ansi_invert; + + ptr_list journals; + ptr_list parsers; + + session_t(); + virtual ~session_t() { + TRACE_DTOR(session_t); + } + + journal_t * create_journal() { + journal_t * journal = new journal_t; + journals.push_back(journal); + return journal; + } + void close_journal(journal_t * journal) { + for (ptr_list::iterator i = journals.begin(); + i != journals.end(); + i++) + if (&*i == journal) { + journals.erase(i); + return; + } + assert(false); + checked_delete(journal); + } + + std::size_t read_journal(std::istream& in, + const path& pathname, + xml::builder_t& builder); + std::size_t read_journal(const path& pathname, + xml::builder_t& builder); + + void read_init(); + + std::size_t read_data(xml::builder_t& builder, + journal_t * journal, + const string& master_account = ""); + + void register_parser(parser_t * parser) { + parsers.push_back(parser); + } + void unregister_parser(parser_t * parser) { + for (ptr_list::iterator i = parsers.begin(); + i != parsers.end(); + i++) + if (&*i == parser) { + parsers.erase(i); + return; + } + assert(false); + checked_delete(parser); + } + + // + // Scope members + // + + virtual xml::xpath_t::ptr_op_t lookup(const string& name); + + // + // Debug options + // + + value_t option_trace_(xml::xpath_t::scope_t& locals) { + return NULL_VALUE; + } + value_t option_debug_(xml::xpath_t::scope_t& locals) { + return NULL_VALUE; + } + + value_t option_verify(xml::xpath_t::scope_t&) { + return NULL_VALUE; + } + value_t option_verbose(xml::xpath_t::scope_t&) { +#if defined(LOGGING_ON) + if (_log_level < LOG_INFO) + _log_level = LOG_INFO; +#endif + return NULL_VALUE; + } + + // + // Option handlers + // + + value_t option_file_(xml::xpath_t::call_scope_t& args) { + assert(args.size() == 1); + data_file = args[0].as_string(); + return NULL_VALUE; + } + +#if 0 +#if defined(USE_BOOST_PYTHON) + value_t option_import_(xml::xpath_t::call_scope_t& args) { + python_import(optarg); + return NULL_VALUE; + } + value_t option_import_stdin(xml::xpath_t::call_scope_t& args) { + python_eval(std::cin, PY_EVAL_MULTI); + return NULL_VALUE; + } +#endif +#endif +}; + +/** + * This sets the current session context, transferring all static + * globals to point at the data structures related to this session. + * Although Ledger itself is not thread-safe, by locking, switching + * session context, then unlocking after the operation is done, + * multiple threads can sequentially make use of the library. Thus, a + * session_t maintains all of the information relating to a single + * usage of the Ledger library. + */ +void set_session_context(session_t * session = NULL); + +} // namespace ledger + +#endif // _SESSION_H -- cgit v1.2.3