Reworked how the REPL is handled.
This commit is contained in:
parent
4f174014b9
commit
c58cd88299
5 changed files with 245 additions and 167 deletions
323
src/main.cc
323
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++;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
63
src/report.h
63
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
11
src/work.cc
11
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue