summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-02-05 02:43:58 -0400
committerJohn Wiegley <johnw@newartisans.com>2009-02-05 02:45:26 -0400
commitc58cd882994d2ad474029a88e36973767847f50d (patch)
tree02bb293a35165eaab970230ea4e913c681c5a943 /src
parent4f174014b923ee154bc892bfdc6a9f13fa858535 (diff)
downloadfork-ledger-c58cd882994d2ad474029a88e36973767847f50d.tar.gz
fork-ledger-c58cd882994d2ad474029a88e36973767847f50d.tar.bz2
fork-ledger-c58cd882994d2ad474029a88e36973767847f50d.zip
Reworked how the REPL is handled.
Diffstat (limited to 'src')
-rw-r--r--src/main.cc321
-rw-r--r--src/report.cc13
-rw-r--r--src/report.h63
-rw-r--r--src/work.cc11
-rw-r--r--src/work.h2
5 files changed, 244 insertions, 166 deletions
diff --git a/src/main.cc b/src/main.cc
index 06ce8563..3e9dbb9c 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -36,27 +36,6 @@
using namespace ledger;
namespace {
- char * stripwhite (char * string)
- {
- if (! string)
- return NULL;
-
- register char *s, *t;
-
- for (s = string; isspace (*s); s++)
- ;
-
- if (*s == 0)
- return (s);
-
- t = s + strlen (s) - 1;
- while (t > s && isspace (*t))
- t--;
- *++t = '\0';
-
- return s;
- }
-
strings_list split_arguments(char * line)
{
strings_list args;
@@ -70,40 +49,53 @@ 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
+
+ if (caught_signal == NONE_CAUGHT) {
+ // Display any pending error context information
+ string context = error_context();
+ if (! context.empty())
+ std::cerr << context << std::endl;
+
+ std::cerr << "Error: " << err.what() << std::endl;
+ } else {
+ caught_signal = NONE_CAUGHT;
+ }
+ }
+
/**
* @return \c true if a command was actually executed; otherwise, it probably
* just resulted in setting some options.
*/
- bool execute_command(session_t& session,
- ledger::strings_list args,
- char ** envp = NULL)
+ void execute_command(session_t& session,
+ report_t& report,
+ strings_list args,
+ bool at_repl)
{
- // 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.
- std::auto_ptr<report_t> manager(new report_t(session));
- report_t& report(*manager.get());
+ // 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));
- session.global_scope = &report;
+ // Process the command verb, arguments and options
+ args = read_command_arguments(*manager.get(), args);
+ if (args.empty())
+ return;
- // Read the user's options, in the following order:
- //
- // 1. environment variables (LEDGER_<option>)
- // 2. initialization file (~/.ledgerrc)
- // 3. command-line (--option or -o)
- //
- // 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.
- if (envp) {
- session.now_at_command_line(false);
- read_environment_settings(report, envp);
- session.read_init();
- }
- session.now_at_command_line(true);
- args = read_command_arguments(report, args);
+ string_iterator arg = args.begin();
+ string verb = *arg++;
// Look for a precommand first, which is defined as any defined function
// whose name starts with "ledger_precmd_". The difference between a
@@ -119,50 +111,43 @@ namespace {
// If such a command is found, create the output stream for the result and
// then invoke the command.
- if (args.empty()) {
- read_journal_files(session, report.account);
- return false;
- } else {
- string_iterator arg = args.begin();
- string verb = *arg++;
-
- if (function_t command = look_for_precommand(report, verb)) {
- // Create the output stream (it might be a file, the console or a PAGER
- // subprocess) and invoke the report command.
- create_output_stream(report);
- invoke_command_verb(report, command, arg, args.end());
- }
- else if (function_t command = look_for_command(report, verb)) {
- // This is regular command verb, so parse the user's data if we
- // haven't already at the beginning of the REPL.
- if (! envp || read_journal_files(session, report.account)) {
- normalize_report_options(report, verb); // jww (2009-02-02): a hack
-
- // Create the output stream (it might be a file, the console or a
- // PAGER subprocess) and invoke the report command.
- create_output_stream(report);
- invoke_command_verb(report, command, arg, args.end());
- }
- }
- else {
- throw_(std::logic_error, "Unrecognized command '" << verb << "'");
- }
+ function_t command;
+ bool is_precommand = false;
- session.global_scope = NULL;
+ if (bool(command = look_for_precommand(*manager.get(), verb)))
+ is_precommand = true;
+ else if (! bool(command = look_for_command(*manager.get(), verb)))
+ throw_(std::logic_error, "Unrecognized command '" << verb << "'");
- return true;
+ // If it is not a pre-command, then parse the user's ledger data at this
+ // time if not done alreday (i.e., if not at a REPL). Then patch up the
+ // report options based on the command verb.
+
+ if (! is_precommand) {
+ if (! at_repl)
+ read_journal_files(session, manager->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);
}
+
+ // Create the output stream (it might be a file, the console or a PAGER
+ // subprocess) and invoke the report command.
+
+ create_output_stream(*manager.get()); // closed by auto_ptr destructor
+ invoke_command_verb(*manager.get(), command, arg, args.end());
}
- int execute_command_wrapper(session_t& session,
- ledger::strings_list args,
- char ** envp = NULL)
+ int execute_command_wrapper(session_t& session,
+ report_t& report,
+ strings_list args,
+ bool at_repl)
{
int status = 1;
try {
- if (! execute_command(session, args, envp))
- return -1;
+ execute_command(session, report, args, at_repl);
// If we've reached this point, everything succeeded fine. Ledger uses
// exceptions to notify of error conditions, so if you're using gdb,
@@ -170,22 +155,7 @@ namespace {
status = 0;
}
catch (const std::exception& err) {
- std::cout.flush(); // first display anything that was pending
-
- if (caught_signal == NONE_CAUGHT) {
- // Display any pending error context information
- string context = error_context();
- if (! context.empty())
- std::cerr << context << std::endl;
-
- std::cerr << "Error: " << err.what() << std::endl;
- } else {
- caught_signal = NONE_CAUGHT;
- }
- }
- catch (int _status) {
- status = _status; // used for a "quick" exit, and is used only
- // if help text (such as --help) was displayed
+ report_error(err);
}
return status;
}
@@ -193,7 +163,7 @@ namespace {
int main(int argc, char * argv[], char * envp[])
{
- session_t * session = NULL;
+ int status;
// The very first thing we do is handle some very special command-line
// options, since they affect how the environment is setup:
@@ -211,75 +181,134 @@ int main(int argc, char * argv[], char * envp[])
std::ios::sync_with_stdio(false);
filesystem::path::default_name_check(filesystem::portable_posix_name);
+ std::signal(SIGINT, sigint_handler);
+ std::signal(SIGPIPE, sigpipe_handler);
+
// Create the session object, which maintains nearly all state relating to
// this invocation of Ledger; and register all known journal parsers.
- session = new LEDGER_SESSION_T;
+ session_t * session = new LEDGER_SESSION_T;
set_session_context(session);
- strings_list cmd_args;
- for (int i = 1; i < argc; i++)
- cmd_args.push_back(argv[i]);
+ // 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));
- int status = execute_command_wrapper(*session, cmd_args, envp);
- if (status == -1) { // no command was given; enter the REPL
- session->option_version(*session);
-
- std::signal(SIGINT, sigint_handler);
- std::signal(SIGPIPE, sigpipe_handler);
+ try {
+ // Read the user's options, in the following order:
+ //
+ // 1. environment variables (LEDGER_<option>)
+ // 2. initialization file (~/.ledgerrc)
+ // 3. command-line (--option or -o)
+ //
+ // 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);
+
+ // Construct an STL-style argument list from the process command arguments
+ strings_list args;
+ for (int i = 1; i < argc; i++)
+ 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);
+
+ if (! args.empty()) { // user has invoke a command-line verb
+ status = execute_command_wrapper(*session, report_stack.front(), args,
+ false);
+ } else {
+ // Commence the REPL by displaying the current Ledger version
+ session->option_version(*session);
+
+ read_journal_files(*session, report_stack.front().account);
+
+ bool exit_loop = false;
#ifdef HAVE_LIBEDIT
- rl_readline_name = const_cast<char *>("Ledger");
+ rl_readline_name = const_cast<char *>("Ledger");
#if 0
- rl_attempted_completion_function = ledger_completion;
+ // jww (2009-02-05): NYI
+ rl_attempted_completion_function = ledger_completion;
#endif
- while (char * line = stripwhite(readline("==> "))) {
- char * expansion = NULL;
- int result;
+ while (char * p = readline(prompt_string(report_stack))) {
+ char * expansion = NULL;
+ int result;
- if (std::strcmp(line, "quit") == 0) {
- std::free(line);
- break;
- }
+ result = history_expand(skip_ws(p), &expansion);
- result = history_expand(line, &expansion);
+ if (result < 0 || result == 2) {
+ if (expansion)
+ std::free(expansion);
+ std::free(p);
+ throw_(std::logic_error,
+ "Failed to expand history reference '" << p << "'");
+ }
+ else if (expansion) {
+ add_history(expansion);
+ }
- if (result < 0 || result == 2) {
- std::free(line);
- throw_(std::logic_error,
- "Failed to expand history reference '" << line << "'");
- }
- else if (expansion) {
- add_history(expansion);
+#else // HAVE_LIBEDIT
- strings_list line_argv = split_arguments(line);
- execute_command_wrapper(*session, line_argv);
+ while (! std::cin.eof()) {
+ std::cout << prompt_string(report_stack);
+ char line[1024];
+ std::cin.getline(line, 1023);
- std::free(expansion);
- }
- std::free(line);
- }
+ char * p = skip_ws(line);
-#else // HAVE_LIBEDIT
+#endif // HAVE_LIBEDIT
- while (! std::cin.eof()) {
- std::cout << "--> ";
- char line[1024];
- std::cin.getline(line, 1023);
+ bool do_command = true;
- char * p = stripwhite(line);
- if (*p) {
- if (std::strcmp(p, "quit") == 0)
- break;
+ check_for_signal();
- execute_command_wrapper(*session, split_arguments(line));
- }
- }
+ if (! *p) {
+ do_command = false;
+ }
+ else if (std::strncmp(p, "quit", 4) == 0) {
+ exit_loop = true;
+ do_command = false;
+ }
+ 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)
+ std::free(expansion);
+ std::free(p);
#endif // HAVE_LIBEDIT
- status = 0; // report success
+ if (exit_loop)
+ break;
+ }
+
+ status = 0; // report success
+ }
+ }
+ catch (const std::exception& err) {
+ report_error(err);
+ }
+ catch (int _status) {
+ status = _status; // used for a "quick" exit, and is used only
+ // if help text (such as --help) was displayed
}
// If memory verification is being performed (which can be very slow), clean
diff --git a/src/report.cc b/src/report.cc
index de13d048..d4158638 100644
--- a/src/report.cc
+++ b/src/report.cc
@@ -248,10 +248,11 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
break;
case 'p':
- if (*(p + 1) == '\0' ||
- std::strcmp(p, "print") == 0)
+ 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,7 +345,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
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::option_ignore_);
+ return MAKE_FUNCTOR(report_t::ignore);
break;
case 'e':
@@ -440,7 +441,7 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
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::option_ignore_);
+ return MAKE_FUNCTOR(report_t::ignore);
break;
case 'u':
@@ -450,9 +451,9 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
case 'v':
if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
- return MAKE_FUNCTOR(report_t::option_ignore);
+ return MAKE_FUNCTOR(report_t::ignore);
else if (std::strcmp(p, "verify") == 0)
- return MAKE_FUNCTOR(report_t::option_ignore);
+ return MAKE_FUNCTOR(report_t::ignore);
break;
case 'w':
diff --git a/src/report.h b/src/report.h
index f2e6626a..5f2ac894 100644
--- a/src/report.h
+++ b/src/report.h
@@ -100,7 +100,7 @@ namespace ledger {
*
* Long.
*/
-class report_t : public noncopyable, public scope_t
+class report_t : public scope_t
{
report_t();
@@ -191,6 +191,62 @@ public:
TRACE_CTOR(report_t, "session_t&");
}
+ report_t(const report_t& other)
+ : scope_t(),
+
+ output_file(other.output_file),
+
+ format_string(other.format_string),
+ output_date_format(other.output_date_format),
+ predicate(other.predicate),
+ secondary_predicate(other.secondary_predicate),
+ display_predicate(other.display_predicate),
+ report_period(other.report_period),
+ report_period_sort(other.report_period_sort),
+ sort_string(other.sort_string),
+ descend_expr(other.descend_expr),
+ forecast_limit(other.forecast_limit),
+ reconcile_balance(other.reconcile_balance),
+ reconcile_date(other.reconcile_date),
+
+ amount_expr(other.amount_expr),
+ total_expr(other.total_expr),
+ display_total(other.display_total),
+
+ budget_flags(other.budget_flags),
+
+ head_entries(other.head_entries),
+ tail_entries(other.tail_entries),
+
+ show_collapsed(other.show_collapsed),
+ show_subtotal(other.show_subtotal),
+ show_totals(other.show_totals),
+ show_related(other.show_related),
+ show_all_related(other.show_all_related),
+ show_inverted(other.show_inverted),
+ show_empty(other.show_empty),
+ days_of_the_week(other.days_of_the_week),
+ by_payee(other.by_payee),
+ comm_as_payee(other.comm_as_payee),
+ code_as_payee(other.code_as_payee),
+ show_revalued(other.show_revalued),
+ show_revalued_only(other.show_revalued_only),
+ entry_sort(other.entry_sort),
+ sort_all(other.sort_all),
+ anonymize(other.anonymize),
+ use_effective_date(other.use_effective_date),
+
+ what_to_keep(other.what_to_keep),
+
+ account(other.account),
+
+ raw_mode(other.raw_mode),
+
+ session(other.session)
+ {
+ TRACE_CTOR(report_t, "copy");
+ }
+
virtual ~report_t() {
TRACE_DTOR(report_t);
output_stream.close();
@@ -266,10 +322,7 @@ public:
//
// Report filtering
- value_t option_ignore(call_scope_t& args) {
- return true;
- }
- value_t option_ignore_(call_scope_t& args) {
+ value_t ignore(call_scope_t& args) {
return true;
}
diff --git a/src/work.cc b/src/work.cc
index e52618d7..4d9b6649 100644
--- a/src/work.cc
+++ b/src/work.cc
@@ -125,13 +125,12 @@ function_t look_for_precommand(report_t& report, const string& verb)
return function_t();
}
-journal_t * read_journal_files(session_t& session, const string& account)
+void read_journal_files(session_t& session, const string& account)
{
INFO_START(journal, "Read journal file");
- journal_t * journal(session.create_journal());
-
- std::size_t count = session.read_data(*journal, account);
+ std::size_t count = session.read_data(*session.create_journal(),
+ account);
if (count == 0)
throw_(parse_error, "Failed to locate any journal entries; "
"did you specify a valid file with -f?");
@@ -146,8 +145,6 @@ journal_t * read_journal_files(session_t& session, const string& account)
TRACE_FINISH(entries, 1);
TRACE_FINISH(session_parser, 1);
TRACE_FINISH(parsing_total, 1);
-
- return journal;
}
function_t look_for_command(report_t& report, const string& verb)
@@ -160,7 +157,6 @@ function_t look_for_command(report_t& report, const string& verb)
void normalize_report_options(report_t& report, const string& verb)
{
-#if 1
// Patch up some of the reporting options based on what kind of
// command it was.
@@ -205,7 +201,6 @@ void normalize_report_options(report_t& report, const string& verb)
report.display_predicate = "amount";
}
}
-#endif
if (! report.report_period.empty() && ! report.sort_all)
report.entry_sort = true;
diff --git a/src/work.h b/src/work.h
index c09766fd..477d8e84 100644
--- a/src/work.h
+++ b/src/work.h
@@ -48,7 +48,7 @@ void read_environment_settings(report_t& report, char * envp[]);
strings_list read_command_arguments(report_t& report, strings_list args);
void normalize_session_options(session_t& session);
function_t look_for_precommand(report_t& report, const string& verb);
-journal_t * read_journal_files(session_t& session, const string& account);
+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);