Reworked how the REPL is handled.

This commit is contained in:
John Wiegley 2009-02-05 02:43:58 -04:00
parent 4f174014b9
commit c58cd88299
5 changed files with 245 additions and 167 deletions

View file

@ -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++;
function_t command;
bool is_precommand = false;
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
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 << "'");
// 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 << "'");
}
// 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.
session.global_scope = NULL;
if (! is_precommand) {
if (! at_repl)
read_journal_files(session, manager->account);
return true;
// 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) {
std::free(line);
throw_(std::logic_error,
"Failed to expand history reference '" << line << "'");
}
else if (expansion) {
add_history(expansion);
strings_list line_argv = split_arguments(line);
execute_command_wrapper(*session, line_argv);
std::free(expansion);
}
std::free(line);
}
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);
}
#else // HAVE_LIBEDIT
while (! std::cin.eof()) {
std::cout << "--> ";
char line[1024];
std::cin.getline(line, 1023);
while (! std::cin.eof()) {
std::cout << prompt_string(report_stack);
char line[1024];
std::cin.getline(line, 1023);
char * p = stripwhite(line);
if (*p) {
if (std::strcmp(p, "quit") == 0)
break;
execute_command_wrapper(*session, split_arguments(line));
}
}
char * p = skip_ws(line);
#endif // HAVE_LIBEDIT
status = 0; // report success
bool do_command = true;
check_for_signal();
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
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

View file

@ -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':

View file

@ -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;
}

View file

@ -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;

View file

@ -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);