ledger/parser.cc
John Wiegley f691735c6c Restructed the code that it can build and be used as a shared library.
The command-line version is still statically bound in the build
process by default (for the sake of speed).
2008-04-13 02:41:21 -04:00

241 lines
6.2 KiB
C++

#include "parser.h"
#include "journal.h"
#include "config.h"
#include <fstream>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
namespace ledger {
typedef std::list<parser_t *> parsers_list;
static parsers_list * parsers = NULL;
void initialize_parser_support()
{
parsers = new parsers_list;
}
void shutdown_parser_support()
{
if (parsers) {
delete parsers;
parsers = NULL;
}
}
bool register_parser(parser_t * parser)
{
parsers_list::iterator i;
for (i = parsers->begin(); i != parsers->end(); i++)
if (*i == parser)
break;
if (i != parsers->end())
return false;
parsers->push_back(parser);
return true;
}
bool unregister_parser(parser_t * parser)
{
parsers_list::iterator i;
for (i = parsers->begin(); i != parsers->end(); i++)
if (*i == parser)
break;
if (i == parsers->end())
return false;
parsers->erase(i);
return true;
}
unsigned int parse_journal(std::istream& in,
journal_t * journal,
account_t * master,
const std::string * original_file)
{
if (! master)
master = journal->master;
for (parsers_list::iterator i = parsers->begin();
i != parsers->end();
i++)
if ((*i)->test(in))
return (*i)->parse(in, journal, master, original_file);
return 0;
}
unsigned int parse_journal_file(const std::string& path,
journal_t * journal,
account_t * master,
const std::string * original_file)
{
journal->sources.push_back(path);
if (access(path.c_str(), R_OK) == -1)
throw error(std::string("Cannot read file '") + path + "'");
if (! original_file)
original_file = &path;
std::ifstream stream(path.c_str());
return parse_journal(stream, journal, master, original_file);
}
unsigned int parse_ledger_data(journal_t * journal,
const std::string& data_file,
const std::string& init_file,
const std::string& price_db,
bool use_cache,
const std::string& cache_file,
bool * cache_dirty,
parser_t * cache_parser,
parser_t * xml_parser,
parser_t * stdin_parser,
const std::string& default_account)
{
unsigned int entry_count = 0;
DEBUG_PRINT("ledger.config.cache", "3. use_cache = " << use_cache);
if (! init_file.empty() && access(init_file.c_str(), R_OK) != -1) {
if (parse_journal_file(init_file, journal) ||
journal->auto_entries.size() > 0 ||
journal->period_entries.size() > 0)
throw error(std::string("Entries found in initialization file '") +
init_file + "'");
journal->sources.pop_front(); // remove init file
}
if (use_cache && ! cache_file.empty() && ! data_file.empty()) {
DEBUG_PRINT("ledger.config.cache", "using_cache " << cache_file);
if (cache_dirty)
*cache_dirty = true;
if (access(cache_file.c_str(), R_OK) != -1) {
std::ifstream stream(cache_file.c_str());
if (cache_parser && cache_parser->test(stream)) {
std::string price_db_orig = journal->price_db;
journal->price_db = price_db;
entry_count += cache_parser->parse(stream, journal, NULL, &data_file);
if (entry_count > 0) {
if (cache_dirty)
*cache_dirty = false;
} else {
journal->price_db = price_db_orig;
}
}
}
}
if (entry_count == 0 && ! data_file.empty()) {
account_t * acct = NULL;
if (! default_account.empty())
acct = journal->find_account(default_account);
journal->price_db = price_db;
if (! journal->price_db.empty() &&
access(journal->price_db.c_str(), R_OK) != -1) {
if (parse_journal_file(journal->price_db, journal)) {
throw error("Entries not allowed in price history file");
} else {
DEBUG_PRINT("ledger.config.cache",
"read price database " << journal->price_db);
journal->sources.pop_back();
}
}
DEBUG_PRINT("ledger.config.cache",
"rejected cache, parsing " << data_file);
if (data_file == "-") {
use_cache = false;
journal->sources.push_back("<stdin>");
#if 0
if (xml_parser && std::cin.peek() == '<')
entry_count += xml_parser->parse(std::cin, journal, acct);
else if (stdin_parser)
#endif
entry_count += stdin_parser->parse(std::cin, journal, acct);
}
else if (access(data_file.c_str(), R_OK) != -1) {
entry_count += parse_journal_file(data_file, journal, acct);
if (! journal->price_db.empty())
journal->sources.push_back(journal->price_db);
}
}
VALIDATE(journal->valid());
return entry_count;
}
extern parser_t * binary_parser_ptr;
extern parser_t * xml_parser_ptr;
extern parser_t * textual_parser_ptr;
unsigned int parse_ledger_data(journal_t * journal, config_t& config)
{
return parse_ledger_data(journal, config.data_file, config.init_file,
config.price_db, config.use_cache,
config.cache_file, &config.cache_dirty,
binary_parser_ptr, xml_parser_ptr,
textual_parser_ptr, config.account);
}
} // namespace ledger
#ifdef USE_BOOST_PYTHON
#include <boost/python.hpp>
#include <Python.h>
using namespace boost::python;
using namespace ledger;
struct parser_wrap : public parser_t
{
PyObject * self;
parser_wrap(PyObject * self_) : self(self_) {}
virtual bool test(std::istream& in) const {
return call_method<bool>(self, "test", in);
}
virtual unsigned int parse(std::istream& in,
journal_t * journal,
account_t * master = NULL,
const std::string * original_file = NULL) {
return call_method<unsigned int>(self, "__call__", in, journal, master,
original_file);
}
};
BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4)
BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads,
parse_journal_file, 2, 4)
BOOST_PYTHON_FUNCTION_OVERLOADS(parse_ledger_data_overloads,
parse_ledger_data, 1, 2)
void export_parser() {
class_< parser_t, parser_wrap, boost::noncopyable > ("Parser")
;
def("register_parser", register_parser);
def("unregister_parser", unregister_parser);
def("parse_journal", parse_journal, parse_journal_overloads());
def("parse_journal_file", parse_journal_file, parse_journal_file_overloads());
#if 0
def("parse_ledger_data", parse_ledger_data, parse_ledger_data_overloads());
#endif
}
#endif // USE_BOOST_PYTHON