summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-02-05 03:49:49 -0400
committerJohn Wiegley <johnw@newartisans.com>2009-02-05 03:49:49 -0400
commit37006741d62d630f8d0ef58fece07af15dcf82c7 (patch)
treefe008bda4570cc8e4e0a95651a6e3761b19ee36c /src
parent327fdca8f43bee675c0e1bfd5e3c3dca6bafc5d0 (diff)
downloadfork-ledger-37006741d62d630f8d0ef58fece07af15dcf82c7.tar.gz
fork-ledger-37006741d62d630f8d0ef58fece07af15dcf82c7.tar.bz2
fork-ledger-37006741d62d630f8d0ef58fece07af15dcf82c7.zip
Support using Ledger as a script interpretor.
The file must begin with '#!/usr/bin/env ledger --script'. You can add a -f option to the options, but it must come before --script.
Diffstat (limited to 'src')
-rw-r--r--src/main.cc294
-rw-r--r--src/report.cc13
-rw-r--r--src/report.h4
-rw-r--r--src/work.cc49
-rw-r--r--src/work.h11
5 files changed, 233 insertions, 138 deletions
diff --git a/src/main.cc b/src/main.cc
index 3e9dbb9c..207582e2 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -36,6 +36,142 @@
using namespace ledger;
namespace {
+ class global_scope_t : public noncopyable, public scope_t
+ {
+ scoped_ptr<session_t> session_ptr;
+ ptr_list<report_t> report_stack;
+
+ public:
+ path script_file;
+
+ global_scope_t() {
+ session_ptr.reset(new LEDGER_SESSION_T);
+ set_session_context(session_ptr.get());
+
+ // Create the report object, which maintains state relating to each
+ // command invocation. Because we're running from main(), the
+ // distinction between session and report doesn't really matter, but if
+ // a GUI were calling into Ledger it would have one session object per
+ // open document, with a separate report_t object for each report it
+ // generated.
+ report_stack.push_front(new report_t(*session_ptr.get()));
+ }
+ ~global_scope_t() {
+ // 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);
+ INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
+ shutdown_memory_tracing();
+ } else {
+ // Don't free anything, just let it all leak.
+ INFO("Ledger ended");
+ }
+ }
+
+ char * prompt_string()
+ {
+ static char prompt[32];
+ std::size_t i;
+ for (i = 0; i < report_stack.size(); i++)
+ prompt[i] = ']';
+ prompt[i++] = ' ';
+ prompt[i] = '\0';
+ return prompt;
+ }
+
+ session_t& session() {
+ return *session_ptr.get();
+ }
+ report_t& report() {
+ return report_stack.front();
+ }
+
+ void push_report() {
+ report_stack.push_front(new report_t(report_stack.front()));
+ }
+ void pop_report() {
+ if (! report_stack.empty())
+ report_stack.pop_front();
+ }
+
+ value_t push_report_cmd(call_scope_t&) {
+ // Make a copy at position 2, because the topmost report object has an
+ // open output stream at this point. We want it to get popped off as
+ // soon as this command terminate so that the stream is closed cleanly.
+ report_stack.insert(++report_stack.begin(),
+ new report_t(report_stack.front()));
+ return true;
+ }
+ value_t pop_report_cmd(call_scope_t&) {
+ pop_report();
+ return true;
+ }
+
+ value_t option_script_(call_scope_t& args) {
+ script_file = args[0].as_string();
+ return true;
+ }
+
+ value_t ignore(call_scope_t&) {
+ return true;
+ }
+
+ virtual expr_t::ptr_op_t lookup(const string& name)
+ {
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'l':
+ if (std::strncmp(p, "ledger_precmd_", 14) == 0) {
+ p = p + 14;
+ switch (*p) {
+ case 'p':
+ if (std::strcmp(p, "push") == 0)
+ return MAKE_FUNCTOR(global_scope_t::push_report_cmd);
+ else if (std::strcmp(p, "pop") == 0)
+ return MAKE_FUNCTOR(global_scope_t::pop_report_cmd);
+ break;
+ }
+ }
+ break;
+
+ case 'o':
+ if (std::strncmp(p, "opt_", 4) == 0) {
+ p = p + 4;
+ switch (*p) {
+ case 'd':
+ if (std::strcmp(p, "debug_") == 0)
+ return MAKE_FUNCTOR(global_scope_t::ignore);
+ break;
+
+ case 's':
+ if (std::strcmp(p, "script_") == 0)
+ return MAKE_FUNCTOR(global_scope_t::option_script_);
+ break;
+
+ case 't':
+ if (std::strcmp(p, "trace_") == 0)
+ return MAKE_FUNCTOR(global_scope_t::ignore);
+ break;
+
+ case 'v':
+ if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
+ return MAKE_FUNCTOR(global_scope_t::ignore);
+ else if (std::strcmp(p, "verify") == 0)
+ return MAKE_FUNCTOR(global_scope_t::ignore);
+ break;
+ }
+ }
+ }
+
+ // If you're wondering how symbols from report() will be found, it's
+ // because of the bind_scope_t object in execute_command() below.
+ return expr_t::ptr_op_t();
+ }
+ };
+
strings_list split_arguments(char * line)
{
strings_list args;
@@ -49,17 +185,6 @@ namespace {
return args;
}
- char * prompt_string(const ptr_list<report_t>& report_stack)
- {
- static char prompt[32];
- std::size_t i;
- for (i = 0; i < report_stack.size(); i++)
- prompt[i] = ']';
- prompt[i++] = ' ';
- prompt[i] = '\0';
- return prompt;
- }
-
void report_error(const std::exception& err)
{
std::cout.flush(); // first display anything that was pending
@@ -80,17 +205,12 @@ namespace {
* @return \c true if a command was actually executed; otherwise, it probably
* just resulted in setting some options.
*/
- void execute_command(session_t& session,
- report_t& report,
- strings_list args,
- bool at_repl)
+ void execute_command(global_scope_t& global_scope,
+ strings_list args,
+ bool at_repl)
{
- // Create a new report command object based on the current one, so that
- // the next command's option don't corrupt state.
- std::auto_ptr<report_t> manager(new report_t(report));
-
// Process the command verb, arguments and options
- args = read_command_arguments(*manager.get(), args);
+ args = read_command_arguments(global_scope.report(), args);
if (args.empty())
return;
@@ -111,12 +231,13 @@ namespace {
// If such a command is found, create the output stream for the result and
// then invoke the command.
- function_t command;
- bool is_precommand = false;
+ function_t command;
+ bool is_precommand = false;
+ bind_scope_t bound_scope(global_scope, global_scope.report());
- if (bool(command = look_for_precommand(*manager.get(), verb)))
+ if (bool(command = look_for_precommand(bound_scope, verb)))
is_precommand = true;
- else if (! bool(command = look_for_command(*manager.get(), verb)))
+ else if (! bool(command = look_for_command(bound_scope, verb)))
throw_(std::logic_error, "Unrecognized command '" << verb << "'");
// If it is not a pre-command, then parse the user's ledger data at this
@@ -125,29 +246,45 @@ namespace {
if (! is_precommand) {
if (! at_repl)
- read_journal_files(session, manager->account);
+ read_journal_files(global_scope.session(),
+ global_scope.report().account);
// jww (2009-02-02): This is a complete hack, and a leftover from long,
// long ago. The question is, how best to remove its necessity...
- normalize_report_options(*manager.get(), verb);
+ normalize_report_options(global_scope.report(), verb);
}
// Create the output stream (it might be a file, the console or a PAGER
- // subprocess) and invoke the report command.
+ // subprocess) and invoke the report command. The output stream is closed
+ // by the caller of this function.
+
+ global_scope.report()
+ .output_stream.initialize(global_scope.report().output_file,
+ global_scope.session().pager_path);
+
+ // Create an argument scope containing the report command's arguments, and
+ // then invoke the command. The bound scope causes lookups to happen
+ // first in the global scope, and then in the report scope.
+
+ call_scope_t command_args(bound_scope);
+ for (string_iterator i = arg; i != args.end(); i++)
+ command_args.push_back(string_value(*i));
- create_output_stream(*manager.get()); // closed by auto_ptr destructor
- invoke_command_verb(*manager.get(), command, arg, args.end());
+ INFO_START(command, "Finished executing command");
+ command(command_args);
+ INFO_FINISH(command);
}
- int execute_command_wrapper(session_t& session,
- report_t& report,
- strings_list args,
- bool at_repl)
+ int execute_command_wrapper(global_scope_t& global_scope,
+ strings_list args,
+ bool at_repl)
{
int status = 1;
try {
- execute_command(session, report, args, at_repl);
+ global_scope.push_report();
+ execute_command(global_scope, args, at_repl);
+ global_scope.pop_report();
// If we've reached this point, everything succeeded fine. Ledger uses
// exceptions to notify of error conditions, so if you're using gdb,
@@ -155,8 +292,10 @@ namespace {
status = 0;
}
catch (const std::exception& err) {
+ global_scope.pop_report();
report_error(err);
}
+
return status;
}
}
@@ -186,16 +325,7 @@ int main(int argc, char * argv[], char * envp[])
// Create the session object, which maintains nearly all state relating to
// this invocation of Ledger; and register all known journal parsers.
- session_t * session = new LEDGER_SESSION_T;
- set_session_context(session);
-
- // Create the report object, which maintains state relating to each command
- // invocation. Because we're running from main(), the distinction between
- // session and report doesn't really matter, but if a GUI were calling into
- // Ledger it would have one session object per open document, with a
- // separate report_t object for each report it generated.
- ptr_list<report_t> report_stack;
- report_stack.push_front(new report_t(*session));
+ std::auto_ptr<global_scope_t> global_scope(new global_scope_t);
try {
// Read the user's options, in the following order:
@@ -207,10 +337,10 @@ int main(int argc, char * argv[], char * envp[])
// Before processing command-line options, we must notify the session object
// that such options are beginning, since options like -f cause a complete
// override of files found anywhere else.
- session->now_at_command_line(false);
- read_environment_settings(report_stack.front(), envp);
- session->read_init();
- session->now_at_command_line(true);
+ global_scope->session().now_at_command_line(false);
+ read_environment_settings(global_scope->report(), envp);
+ global_scope->session().read_init();
+ global_scope->session().now_at_command_line(true);
// Construct an STL-style argument list from the process command arguments
strings_list args;
@@ -218,16 +348,36 @@ int main(int argc, char * argv[], char * envp[])
args.push_back(argv[i]);
// Look for options and a command verb in the command-line arguments
- args = read_command_arguments(report_stack.front(), args);
+ bind_scope_t bound_scope(*global_scope.get(), global_scope->report());
+ args = read_command_arguments(bound_scope, args);
- if (! args.empty()) { // user has invoke a command-line verb
- status = execute_command_wrapper(*session, report_stack.front(), args,
- false);
- } else {
+ if (! global_scope->script_file.empty() &&
+ exists(global_scope->script_file)) {
+
+ read_journal_files(global_scope->session(),
+ global_scope->report().account);
+
+ ifstream in(global_scope->script_file);
+ while (! in.eof()) {
+ char line[1024];
+ in.getline(line, 1023);
+
+ char * p = skip_ws(line);
+ if (*p && *p != '#')
+ execute_command_wrapper(*global_scope, split_arguments(p), true);
+ }
+ status = 0;
+ }
+ else if (! args.empty()) {
+ // User has invoke a verb at the interactive command-line
+ status = execute_command_wrapper(*global_scope, args, false);
+ }
+ else {
// Commence the REPL by displaying the current Ledger version
- session->option_version(*session);
+ global_scope->session().option_version(global_scope->session());
- read_journal_files(*session, report_stack.front().account);
+ read_journal_files(global_scope->session(),
+ global_scope->report().account);
bool exit_loop = false;
@@ -239,7 +389,7 @@ int main(int argc, char * argv[], char * envp[])
rl_attempted_completion_function = ledger_completion;
#endif
- while (char * p = readline(prompt_string(report_stack))) {
+ while (char * p = readline(global_scope->prompt_string())) {
char * expansion = NULL;
int result;
@@ -259,7 +409,7 @@ int main(int argc, char * argv[], char * envp[])
#else // HAVE_LIBEDIT
while (! std::cin.eof()) {
- std::cout << prompt_string(report_stack);
+ std::cout << global_scope->prompt_string();
char line[1024];
std::cin.getline(line, 1023);
@@ -271,24 +421,12 @@ int main(int argc, char * argv[], char * envp[])
check_for_signal();
- if (! *p) {
- do_command = false;
- }
- else if (std::strncmp(p, "quit", 4) == 0) {
- exit_loop = true;
- do_command = false;
+ if (*p && *p != '#') {
+ if (std::strncmp(p, "quit", 4) == 0)
+ exit_loop = true;
+ else
+ execute_command_wrapper(*global_scope, split_arguments(p), true);
}
- else if (std::strncmp(p, "push", 4) == 0) {
- report_stack.push_front(new report_t(report_stack.front()));
- }
- else if (std::strncmp(p, "pop", 3) == 0) {
- report_stack.pop_front();
- do_command = false;
- }
-
- if (do_command)
- execute_command_wrapper(*session, report_stack.front(),
- split_arguments(p), true);
#ifdef HAVE_LIBEDIT
if (expansion)
@@ -316,14 +454,14 @@ int main(int argc, char * argv[], char * envp[])
// 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);
+ global_scope.reset();
- INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
shutdown_memory_tracing();
+ INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
} else {
// Don't free anything, just let it all leak.
+ global_scope.release();
+
INFO("Ledger ended");
}
diff --git a/src/report.cc b/src/report.cc
index d4158638..20c9e8e5 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -251,8 +251,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
if (*(p + 1) == '\0' || std::strcmp(p, "print") == 0)
return WRAP_FUNCTOR
(reporter<>(new format_xacts(*this, FORMAT(print_format))));
- else if (std::strcmp(p, "push") == 0)
- return MAKE_FUNCTOR(report_t::ignore);
break;
case 'r':
@@ -344,8 +342,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return MAKE_FUNCTOR(report_t::option_dow);
else if (std::strcmp(p, "date-format_") == 0)
return MAKE_FUNCTOR(report_t::option_date_format_);
- else if (std::strcmp(p, "debug_") == 0)
- return MAKE_FUNCTOR(report_t::ignore);
break;
case 'e':
@@ -440,8 +436,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return MAKE_FUNCTOR(report_t::option_totals);
else if (std::strcmp(p, "tail_") == 0)
return MAKE_FUNCTOR(report_t::option_tail_);
- else if (std::strcmp(p, "trace_") == 0)
- return MAKE_FUNCTOR(report_t::ignore);
break;
case 'u':
@@ -449,13 +443,6 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
return MAKE_FUNCTOR(report_t::option_uncleared);
break;
- case 'v':
- if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
- return MAKE_FUNCTOR(report_t::ignore);
- else if (std::strcmp(p, "verify") == 0)
- return MAKE_FUNCTOR(report_t::ignore);
- break;
-
case 'w':
if (std::strcmp(p, "weekly") == 0)
return MAKE_FUNCTOR(report_t::option_weekly);
diff --git a/src/report.h b/src/report.h
index 5f2ac894..541122a9 100644
--- a/src/report.h
+++ b/src/report.h
@@ -322,11 +322,11 @@ public:
//
// Report filtering
- value_t ignore(call_scope_t& args) {
+ value_t ignore(call_scope_t&) {
return true;
}
- value_t option_effective(call_scope_t& args) {
+ value_t option_effective(call_scope_t&) {
use_effective_date = true;
return true;
}
diff --git a/src/work.cc b/src/work.cc
index 4d9b6649..8faf6eb0 100644
--- a/src/work.cc
+++ b/src/work.cc
@@ -97,11 +97,11 @@ void read_environment_settings(report_t& report, char * envp[])
TRACE_FINISH(environment, 1);
}
-strings_list read_command_arguments(report_t& report, strings_list args)
+strings_list read_command_arguments(scope_t& scope, strings_list args)
{
TRACE_START(arguments, 1, "Processed command-line arguments");
- strings_list remaining = process_arguments(args, report);
+ strings_list remaining = process_arguments(args, scope);
TRACE_FINISH(arguments, 1);
@@ -117,9 +117,17 @@ void normalize_session_options(session_t& session)
INFO("Journal file is " << pathname.string());
}
-function_t look_for_precommand(report_t& report, const string& verb)
+function_t look_for_precommand(scope_t& scope, const string& verb)
{
- if (expr_t::ptr_op_t def = report.lookup(string("ledger_precmd_") + verb))
+ if (expr_t::ptr_op_t def = scope.lookup(string("ledger_precmd_") + verb))
+ return def->as_function();
+ else
+ return function_t();
+}
+
+function_t look_for_command(scope_t& scope, const string& verb)
+{
+ if (expr_t::ptr_op_t def = scope.lookup(string("ledger_cmd_") + verb))
return def->as_function();
else
return function_t();
@@ -147,14 +155,6 @@ void read_journal_files(session_t& session, const string& account)
TRACE_FINISH(parsing_total, 1);
}
-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)
{
// Patch up some of the reporting options based on what kind of
@@ -206,29 +206,4 @@ void normalize_report_options(report_t& report, const string& verb)
report.entry_sort = true;
}
-void create_output_stream(report_t& report)
-{
- report.output_stream.initialize(report.output_file, report.session.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);
-}
-
} // namespace ledger
diff --git a/src/work.h b/src/work.h
index 477d8e84..7303b661 100644
--- a/src/work.h
+++ b/src/work.h
@@ -45,17 +45,12 @@ typedef std::pair<string_iterator, string_iterator> string_iterator_pair;
void handle_debug_options(int argc, char * argv[]);
void read_environment_settings(report_t& report, char * envp[]);
-strings_list read_command_arguments(report_t& report, strings_list args);
+strings_list read_command_arguments(scope_t& scope, strings_list args);
void normalize_session_options(session_t& session);
-function_t look_for_precommand(report_t& report, const string& verb);
+function_t look_for_precommand(scope_t& scope, const string& verb);
+function_t look_for_command(scope_t& scope, const string& verb);
void read_journal_files(session_t& session, const string& account);
-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);
} // namespace ledger