Added structural support in main() for using a REPL.
This commit is contained in:
parent
b9603a1512
commit
73cf3b01fb
7 changed files with 240 additions and 99 deletions
24
configure.ac
24
configure.ac
|
|
@ -136,6 +136,30 @@ else
|
|||
AC_MSG_FAILURE("Could not find mpfr library 2.4.0 or higher (set CPPFLAGS and LDFLAGS?)")
|
||||
fi
|
||||
|
||||
# check for edit
|
||||
AC_CACHE_CHECK(
|
||||
[if libedit is available],
|
||||
[libedit_avail_cv_],
|
||||
[libedit_save_libs=$LIBS
|
||||
LIBS="-ledit $LIBS"
|
||||
AC_LANG_PUSH(C++)
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM(
|
||||
[[#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <editline/readline.h>]],
|
||||
[[rl_readline_name = "foo";
|
||||
char * line = readline("foo: ");
|
||||
free(line);]])],[libedit_avail_cv_=true],[libedit_avail_cv_=false])
|
||||
AC_LANG_POP
|
||||
LIBS=$libedit_save_libs])
|
||||
|
||||
if [test x$libedit_avail_cv_ = xtrue ]; then
|
||||
LIBS="-ledit $LIBS"
|
||||
AC_DEFINE([HAVE_LIBEDIT], [1], [If the libedit library is available])
|
||||
else
|
||||
AC_MSG_FAILURE("Could not find libedit library (set CPPFLAGS and LDFLAGS?)")
|
||||
fi
|
||||
|
||||
# check for boost_regex
|
||||
AC_CACHE_CHECK(
|
||||
[if boost_regex is available],
|
||||
|
|
|
|||
257
src/main.cc
257
src/main.cc
|
|
@ -33,43 +33,60 @@
|
|||
|
||||
#include "work.h" // This is where the meat of main() is, which
|
||||
// was moved there for the sake of clarity
|
||||
using namespace ledger;
|
||||
|
||||
int main(int argc, char * argv[], char * envp[])
|
||||
{
|
||||
using namespace ledger;
|
||||
namespace {
|
||||
char * stripwhite (char * string)
|
||||
{
|
||||
if (! string)
|
||||
return NULL;
|
||||
|
||||
session_t * session = NULL;
|
||||
report_t * report = NULL;
|
||||
int status = 1;
|
||||
try {
|
||||
// The very first thing we do is handle some very special command-line
|
||||
// options, since they affect how the environment is setup:
|
||||
//
|
||||
// --verify ; turns on memory tracing
|
||||
// --verbose ; turns on logging
|
||||
// --debug CATEGORY ; turns on debug logging
|
||||
// --trace LEVEL ; turns on trace logging
|
||||
handle_debug_options(argc, argv);
|
||||
IF_VERIFY() initialize_memory_tracing();
|
||||
register char *s, *t;
|
||||
|
||||
INFO("Ledger starting");
|
||||
for (s = string; isspace (*s); s++)
|
||||
;
|
||||
|
||||
// Initialize global Boost/C++ environment
|
||||
std::ios::sync_with_stdio(false);
|
||||
filesystem::path::default_name_check(filesystem::portable_posix_name);
|
||||
if (*s == 0)
|
||||
return (s);
|
||||
|
||||
// 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;
|
||||
set_session_context(session);
|
||||
t = s + strlen (s) - 1;
|
||||
while (t > s && isspace (*t))
|
||||
t--;
|
||||
*++t = '\0';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
strings_list split_arguments(char * line)
|
||||
{
|
||||
strings_list args;
|
||||
|
||||
// jww (2009-02-04): This is too naive
|
||||
for (char * p = std::strtok(line, " \t");
|
||||
p;
|
||||
p = std::strtok(NULL, " \t"))
|
||||
args.push_back(p);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
// 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 = new report_t(*session);
|
||||
session->global_scope = report;
|
||||
std::auto_ptr<report_t> manager(new report_t(session));
|
||||
report_t& report(*manager.get());
|
||||
|
||||
session.global_scope = &report;
|
||||
|
||||
// Read the user's options, in the following order:
|
||||
//
|
||||
|
|
@ -80,10 +97,13 @@ 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.
|
||||
read_environment_settings(*report, envp);
|
||||
session->read_init();
|
||||
session->now_at_command_line(true);
|
||||
strings_list args = read_command_line_arguments(*report, argc, argv);
|
||||
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);
|
||||
|
||||
// Look for a precommand first, which is defined as any defined function
|
||||
// whose name starts with "ledger_precmd_". The difference between a
|
||||
|
|
@ -98,56 +118,151 @@ int main(int argc, char * argv[], char * envp[])
|
|||
//
|
||||
// If such a command is found, create the output stream for the result and
|
||||
// then invoke the command.
|
||||
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 (journal_t * journal = read_journal_files(*session, report->account)) {
|
||||
normalize_report_options(*report, verb); // jww (2009-02-02): a hack
|
||||
if (args.empty()) {
|
||||
read_journal_files(session, report.account);
|
||||
return false;
|
||||
} else {
|
||||
string_iterator arg = args.begin();
|
||||
string verb = *arg++;
|
||||
|
||||
// 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());
|
||||
|
||||
// Write out a binary cache of the journal data, if needful and
|
||||
// appropriate to do so.
|
||||
write_binary_cache(*session, journal);
|
||||
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 {
|
||||
throw_(std::logic_error, "Unrecognized command '" << verb << "'");
|
||||
}
|
||||
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 we've reached this point, everything succeeded fine. Ledger uses
|
||||
// exceptions to notify of error conditions, so if you're using gdb, just
|
||||
// type "catch throw" to find the source point of any error.
|
||||
status = 0;
|
||||
// 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 << "'");
|
||||
}
|
||||
|
||||
session.global_scope = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cout.flush(); // first display anything that was pending
|
||||
|
||||
// Display any pending error context information
|
||||
string context = error_context();
|
||||
if (! context.empty())
|
||||
std::cerr << context << std::endl;
|
||||
int execute_command_wrapper(session_t& session,
|
||||
ledger::strings_list args,
|
||||
char ** envp = NULL)
|
||||
{
|
||||
int status = 1;
|
||||
|
||||
try {
|
||||
if (! execute_command(session, args, envp))
|
||||
return -1;
|
||||
|
||||
// If we've reached this point, everything succeeded fine. Ledger uses
|
||||
// exceptions to notify of error conditions, so if you're using gdb,
|
||||
// just type "catch throw" to find the source point of any error.
|
||||
status = 0;
|
||||
}
|
||||
catch (const std::exception& err) {
|
||||
std::cout.flush(); // first display anything that was pending
|
||||
|
||||
// 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;
|
||||
}
|
||||
catch (int _status) {
|
||||
status = _status; // used for a "quick" exit, and is used only
|
||||
std::cerr << "Error: " << err.what() << std::endl;
|
||||
}
|
||||
catch (int _status) {
|
||||
status = _status; // used for a "quick" exit, and is used only
|
||||
// if help text (such as --help) was displayed
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the output stream, waiting on the pager process to exit if need be
|
||||
report->output_stream.close();
|
||||
int main(int argc, char * argv[], char * envp[])
|
||||
{
|
||||
session_t * session = NULL;
|
||||
|
||||
// The very first thing we do is handle some very special command-line
|
||||
// options, since they affect how the environment is setup:
|
||||
//
|
||||
// --verify ; turns on memory tracing
|
||||
// --verbose ; turns on logging
|
||||
// --debug CATEGORY ; turns on debug logging
|
||||
// --trace LEVEL ; turns on trace logging
|
||||
handle_debug_options(argc, argv);
|
||||
IF_VERIFY() initialize_memory_tracing();
|
||||
|
||||
INFO("Ledger starting");
|
||||
|
||||
// Initialize global Boost/C++ environment
|
||||
std::ios::sync_with_stdio(false);
|
||||
filesystem::path::default_name_check(filesystem::portable_posix_name);
|
||||
|
||||
// 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;
|
||||
set_session_context(session);
|
||||
|
||||
strings_list cmd_args;
|
||||
for (int i = 1; i < argc; i++)
|
||||
cmd_args.push_back(argv[i]);
|
||||
|
||||
int status = execute_command_wrapper(*session, cmd_args, envp);
|
||||
if (status == -1) { // no command was given; enter the REPL
|
||||
session->option_version(*session);
|
||||
|
||||
#ifdef HAVE_LIBEDIT
|
||||
|
||||
rl_readline_name = "Ledger";
|
||||
#if 0
|
||||
rl_attempted_completion_function = ledger_completion;
|
||||
#endif
|
||||
|
||||
while (char * line = stripwhite(readline("==> "))) {
|
||||
char * expansion;
|
||||
int result;
|
||||
|
||||
result = history_expand(line, &expansion);
|
||||
|
||||
if (result < 0 || result == 2) {
|
||||
throw_(std::logic_error,
|
||||
"Failed to expand history reference '" << line << "'");
|
||||
} else {
|
||||
add_history(expansion);
|
||||
|
||||
strings_list line_argv = split_arguments(line);
|
||||
execute_command_wrapper(*session, line_argv);
|
||||
}
|
||||
std::free(expansion);
|
||||
|
||||
std::free(line);
|
||||
}
|
||||
|
||||
#else // HAVE_LIBEDIT
|
||||
|
||||
while (! std::cin.eof()) {
|
||||
std::cout << "--> ";
|
||||
char line[1024];
|
||||
std::cin.getline(line, 1023);
|
||||
|
||||
char * p = stripwhite(line);
|
||||
if (*p)
|
||||
execute_command_wrapper(*session, split_arguments(line));
|
||||
}
|
||||
|
||||
#endif // HAVE_LIBEDIT
|
||||
|
||||
status = 0; // report success
|
||||
}
|
||||
|
||||
// If memory verification is being performed (which can be very slow), clean
|
||||
// up everything by closing the session and deleting the session object, and
|
||||
|
|
@ -157,8 +272,6 @@ int main(int argc, char * argv[], char * envp[])
|
|||
set_session_context(NULL);
|
||||
if (session != NULL)
|
||||
checked_delete(session);
|
||||
if (report != NULL)
|
||||
checked_delete(report);
|
||||
|
||||
INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
|
||||
shutdown_memory_tracing();
|
||||
|
|
|
|||
|
|
@ -137,17 +137,20 @@ void process_environment(const char ** envp, const string& tag,
|
|||
}
|
||||
}
|
||||
|
||||
void process_arguments(int, char ** argv, scope_t& scope,
|
||||
std::list<string>& args)
|
||||
strings_list process_arguments(strings_list args, scope_t& scope)
|
||||
{
|
||||
bool anywhere = true;
|
||||
|
||||
for (char ** i = argv; *i; i++) {
|
||||
strings_list remaining;
|
||||
|
||||
for (strings_list::iterator i = args.begin();
|
||||
i != args.end();
|
||||
i++) {
|
||||
DEBUG("option.args", "Examining argument '" << *i << "'");
|
||||
|
||||
if (! anywhere || (*i)[0] != '-') {
|
||||
DEBUG("option.args", " adding to list of real args");
|
||||
args.push_back(*i);
|
||||
remaining.push_back(*i);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -161,20 +164,24 @@ void process_arguments(int, char ** argv, scope_t& scope,
|
|||
|
||||
DEBUG("option.args", " it's an option string");
|
||||
|
||||
char * name = *i + 2;
|
||||
char * value = NULL;
|
||||
if (char * p = std::strchr(name, '=')) {
|
||||
*p++ = '\0';
|
||||
value = p;
|
||||
string opt_name;
|
||||
const char * name = (*i).c_str() + 2;
|
||||
const char * value = NULL;
|
||||
|
||||
if (const char * p = std::strchr(name, '=')) {
|
||||
opt_name = string(name, p - name);
|
||||
value = ++p;
|
||||
DEBUG("option.args", " read option value from option: " << value);
|
||||
} else {
|
||||
opt_name = name;
|
||||
}
|
||||
|
||||
op_bool_tuple opt(find_option(scope, name));
|
||||
op_bool_tuple opt(find_option(scope, opt_name));
|
||||
if (! opt.get<0>())
|
||||
throw_(option_error, "illegal option --" << name);
|
||||
|
||||
if (opt.get<1>() && value == NULL) {
|
||||
value = *++i;
|
||||
value = (*++i).c_str();
|
||||
DEBUG("option.args", " read option value from arg: " << value);
|
||||
if (value == NULL)
|
||||
throw_(option_error, "missing option argument for --" << name);
|
||||
|
|
@ -203,9 +210,9 @@ void process_arguments(int, char ** argv, scope_t& scope,
|
|||
}
|
||||
|
||||
foreach (op_bool_char_tuple& o, option_queue) {
|
||||
char * value = NULL;
|
||||
const char * value = NULL;
|
||||
if (o.get<1>()) {
|
||||
value = *++i;
|
||||
value = (*++i).c_str();
|
||||
DEBUG("option.args", " read option value from arg: " << value);
|
||||
if (value == NULL)
|
||||
throw_(option_error,
|
||||
|
|
@ -216,6 +223,8 @@ void process_arguments(int, char ** argv, scope_t& scope,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remaining;
|
||||
}
|
||||
|
||||
} // namespace ledger
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ void process_option(const string& name, scope_t& scope,
|
|||
void process_environment(const char ** envp, const string& tag,
|
||||
scope_t& scope);
|
||||
|
||||
void process_arguments(int argc, char ** argv, scope_t& scope,
|
||||
std::list<string>& args);
|
||||
strings_list process_arguments(strings_list args, scope_t& scope);
|
||||
|
||||
DECLARE_EXCEPTION(option_error, std::runtime_error);
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ typedef std::ostream::pos_type ostream_pos_type;
|
|||
#include <mpfr.h>
|
||||
#include "sha1.h"
|
||||
|
||||
#ifdef HAVE_LIBEDIT
|
||||
#include <editline/readline.h>
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/any.hpp>
|
||||
|
|
|
|||
13
src/work.cc
13
src/work.cc
|
|
@ -97,22 +97,15 @@ void read_environment_settings(report_t& report, char * envp[])
|
|||
TRACE_FINISH(environment, 1);
|
||||
}
|
||||
|
||||
strings_list
|
||||
read_command_line_arguments(report_t& report, int argc, char * argv[])
|
||||
strings_list read_command_arguments(report_t& report, strings_list args)
|
||||
{
|
||||
TRACE_START(arguments, 1, "Processed command-line arguments");
|
||||
|
||||
strings_list args;
|
||||
process_arguments(argc - 1, argv + 1, report, args);
|
||||
|
||||
if (args.empty()) {
|
||||
help(std::cout);
|
||||
throw int(1);
|
||||
}
|
||||
strings_list remaining = process_arguments(args, report);
|
||||
|
||||
TRACE_FINISH(arguments, 1);
|
||||
|
||||
return args;
|
||||
return remaining;
|
||||
}
|
||||
|
||||
void normalize_session_options(session_t& session)
|
||||
|
|
|
|||
|
|
@ -45,8 +45,7 @@ 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_line_arguments(report_t& report,
|
||||
int argc, char * argv[]);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue