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?)")
|
AC_MSG_FAILURE("Could not find mpfr library 2.4.0 or higher (set CPPFLAGS and LDFLAGS?)")
|
||||||
fi
|
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
|
# check for boost_regex
|
||||||
AC_CACHE_CHECK(
|
AC_CACHE_CHECK(
|
||||||
[if boost_regex is available],
|
[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
|
#include "work.h" // This is where the meat of main() is, which
|
||||||
// was moved there for the sake of clarity
|
// was moved there for the sake of clarity
|
||||||
|
using namespace ledger;
|
||||||
|
|
||||||
int main(int argc, char * argv[], char * envp[])
|
namespace {
|
||||||
{
|
char * stripwhite (char * string)
|
||||||
using namespace ledger;
|
{
|
||||||
|
if (! string)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
session_t * session = NULL;
|
register char *s, *t;
|
||||||
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();
|
|
||||||
|
|
||||||
INFO("Ledger starting");
|
for (s = string; isspace (*s); s++)
|
||||||
|
;
|
||||||
|
|
||||||
// Initialize global Boost/C++ environment
|
if (*s == 0)
|
||||||
std::ios::sync_with_stdio(false);
|
return (s);
|
||||||
filesystem::path::default_name_check(filesystem::portable_posix_name);
|
|
||||||
|
|
||||||
// Create the session object, which maintains nearly all state relating to
|
t = s + strlen (s) - 1;
|
||||||
// this invocation of Ledger; and register all known journal parsers.
|
while (t > s && isspace (*t))
|
||||||
session = new LEDGER_SESSION_T;
|
t--;
|
||||||
set_session_context(session);
|
*++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
|
// Create the report object, which maintains state relating to each
|
||||||
// command invocation. Because we're running from main(), the distinction
|
// command invocation. Because we're running from main(), the distinction
|
||||||
// between session and report doesn't really matter, but if a GUI were
|
// 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,
|
// calling into Ledger it would have one session object per open document,
|
||||||
// with a separate report_t object for each report it generated.
|
// with a separate report_t object for each report it generated.
|
||||||
report = new report_t(*session);
|
std::auto_ptr<report_t> manager(new report_t(session));
|
||||||
session->global_scope = report;
|
report_t& report(*manager.get());
|
||||||
|
|
||||||
|
session.global_scope = &report;
|
||||||
|
|
||||||
// Read the user's options, in the following order:
|
// 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
|
// Before processing command-line options, we must notify the session
|
||||||
// object that such options are beginning, since options like -f cause a
|
// object that such options are beginning, since options like -f cause a
|
||||||
// complete override of files found anywhere else.
|
// complete override of files found anywhere else.
|
||||||
read_environment_settings(*report, envp);
|
if (envp) {
|
||||||
session->read_init();
|
session.now_at_command_line(false);
|
||||||
session->now_at_command_line(true);
|
read_environment_settings(report, envp);
|
||||||
strings_list args = read_command_line_arguments(*report, argc, argv);
|
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
|
// Look for a precommand first, which is defined as any defined function
|
||||||
// whose name starts with "ledger_precmd_". The difference between a
|
// 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
|
// If such a command is found, create the output stream for the result and
|
||||||
// then invoke the command.
|
// then invoke the command.
|
||||||
string_iterator arg = args.begin();
|
|
||||||
string verb = *arg++;
|
|
||||||
|
|
||||||
if (function_t command = look_for_precommand(*report, verb)) {
|
if (args.empty()) {
|
||||||
// Create the output stream (it might be a file, the console or a PAGER
|
read_journal_files(session, report.account);
|
||||||
// subprocess) and invoke the report command.
|
return false;
|
||||||
create_output_stream(*report);
|
} else {
|
||||||
invoke_command_verb(*report, command, arg, args.end());
|
string_iterator arg = args.begin();
|
||||||
}
|
string verb = *arg++;
|
||||||
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
|
|
||||||
|
|
||||||
// Create the output stream (it might be a file, the console or a
|
if (function_t command = look_for_precommand(report, verb)) {
|
||||||
// PAGER subprocess) and invoke the report command.
|
// Create the output stream (it might be a file, the console or a PAGER
|
||||||
create_output_stream(*report);
|
// subprocess) and invoke the report command.
|
||||||
invoke_command_verb(*report, command, arg, args.end());
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
else if (function_t command = look_for_command(report, verb)) {
|
||||||
else {
|
// This is regular command verb, so parse the user's data if we
|
||||||
throw_(std::logic_error, "Unrecognized command '" << verb << "'");
|
// 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
|
// Create the output stream (it might be a file, the console or a
|
||||||
// exceptions to notify of error conditions, so if you're using gdb, just
|
// PAGER subprocess) and invoke the report command.
|
||||||
// type "catch throw" to find the source point of any error.
|
create_output_stream(report);
|
||||||
status = 0;
|
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
|
int execute_command_wrapper(session_t& session,
|
||||||
string context = error_context();
|
ledger::strings_list args,
|
||||||
if (! context.empty())
|
char ** envp = NULL)
|
||||||
std::cerr << context << std::endl;
|
{
|
||||||
|
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;
|
std::cerr << "Error: " << err.what() << std::endl;
|
||||||
}
|
}
|
||||||
catch (int _status) {
|
catch (int _status) {
|
||||||
status = _status; // used for a "quick" exit, and is used only
|
status = _status; // used for a "quick" exit, and is used only
|
||||||
// if help text (such as --help) was displayed
|
// if help text (such as --help) was displayed
|
||||||
|
}
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Close the output stream, waiting on the pager process to exit if need be
|
int main(int argc, char * argv[], char * envp[])
|
||||||
report->output_stream.close();
|
{
|
||||||
|
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
|
// If memory verification is being performed (which can be very slow), clean
|
||||||
// up everything by closing the session and deleting the session object, and
|
// 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);
|
set_session_context(NULL);
|
||||||
if (session != NULL)
|
if (session != NULL)
|
||||||
checked_delete(session);
|
checked_delete(session);
|
||||||
if (report != NULL)
|
|
||||||
checked_delete(report);
|
|
||||||
|
|
||||||
INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
|
INFO("Ledger ended (Boost/libstdc++ may still hold memory)");
|
||||||
shutdown_memory_tracing();
|
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,
|
strings_list process_arguments(strings_list args, scope_t& scope)
|
||||||
std::list<string>& args)
|
|
||||||
{
|
{
|
||||||
bool anywhere = true;
|
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 << "'");
|
DEBUG("option.args", "Examining argument '" << *i << "'");
|
||||||
|
|
||||||
if (! anywhere || (*i)[0] != '-') {
|
if (! anywhere || (*i)[0] != '-') {
|
||||||
DEBUG("option.args", " adding to list of real args");
|
DEBUG("option.args", " adding to list of real args");
|
||||||
args.push_back(*i);
|
remaining.push_back(*i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,20 +164,24 @@ void process_arguments(int, char ** argv, scope_t& scope,
|
||||||
|
|
||||||
DEBUG("option.args", " it's an option string");
|
DEBUG("option.args", " it's an option string");
|
||||||
|
|
||||||
char * name = *i + 2;
|
string opt_name;
|
||||||
char * value = NULL;
|
const char * name = (*i).c_str() + 2;
|
||||||
if (char * p = std::strchr(name, '=')) {
|
const char * value = NULL;
|
||||||
*p++ = '\0';
|
|
||||||
value = p;
|
if (const char * p = std::strchr(name, '=')) {
|
||||||
|
opt_name = string(name, p - name);
|
||||||
|
value = ++p;
|
||||||
DEBUG("option.args", " read option value from option: " << value);
|
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>())
|
if (! opt.get<0>())
|
||||||
throw_(option_error, "illegal option --" << name);
|
throw_(option_error, "illegal option --" << name);
|
||||||
|
|
||||||
if (opt.get<1>() && value == NULL) {
|
if (opt.get<1>() && value == NULL) {
|
||||||
value = *++i;
|
value = (*++i).c_str();
|
||||||
DEBUG("option.args", " read option value from arg: " << value);
|
DEBUG("option.args", " read option value from arg: " << value);
|
||||||
if (value == NULL)
|
if (value == NULL)
|
||||||
throw_(option_error, "missing option argument for --" << name);
|
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) {
|
foreach (op_bool_char_tuple& o, option_queue) {
|
||||||
char * value = NULL;
|
const char * value = NULL;
|
||||||
if (o.get<1>()) {
|
if (o.get<1>()) {
|
||||||
value = *++i;
|
value = (*++i).c_str();
|
||||||
DEBUG("option.args", " read option value from arg: " << value);
|
DEBUG("option.args", " read option value from arg: " << value);
|
||||||
if (value == NULL)
|
if (value == NULL)
|
||||||
throw_(option_error,
|
throw_(option_error,
|
||||||
|
|
@ -216,6 +223,8 @@ void process_arguments(int, char ** argv, scope_t& scope,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return remaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,7 @@ void process_option(const string& name, scope_t& scope,
|
||||||
void process_environment(const char ** envp, const string& tag,
|
void process_environment(const char ** envp, const string& tag,
|
||||||
scope_t& scope);
|
scope_t& scope);
|
||||||
|
|
||||||
void process_arguments(int argc, char ** argv, scope_t& scope,
|
strings_list process_arguments(strings_list args, scope_t& scope);
|
||||||
std::list<string>& args);
|
|
||||||
|
|
||||||
DECLARE_EXCEPTION(option_error, std::runtime_error);
|
DECLARE_EXCEPTION(option_error, std::runtime_error);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,10 @@ typedef std::ostream::pos_type ostream_pos_type;
|
||||||
#include <mpfr.h>
|
#include <mpfr.h>
|
||||||
#include "sha1.h"
|
#include "sha1.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBEDIT
|
||||||
|
#include <editline/readline.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <boost/algorithm/string/classification.hpp>
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/any.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);
|
TRACE_FINISH(environment, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
strings_list
|
strings_list read_command_arguments(report_t& report, strings_list args)
|
||||||
read_command_line_arguments(report_t& report, int argc, char * argv[])
|
|
||||||
{
|
{
|
||||||
TRACE_START(arguments, 1, "Processed command-line arguments");
|
TRACE_START(arguments, 1, "Processed command-line arguments");
|
||||||
|
|
||||||
strings_list args;
|
strings_list remaining = process_arguments(args, report);
|
||||||
process_arguments(argc - 1, argv + 1, report, args);
|
|
||||||
|
|
||||||
if (args.empty()) {
|
|
||||||
help(std::cout);
|
|
||||||
throw int(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
TRACE_FINISH(arguments, 1);
|
TRACE_FINISH(arguments, 1);
|
||||||
|
|
||||||
return args;
|
return remaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
void normalize_session_options(session_t& session)
|
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 handle_debug_options(int argc, char * argv[]);
|
||||||
void read_environment_settings(report_t& report, char * envp[]);
|
void read_environment_settings(report_t& report, char * envp[]);
|
||||||
strings_list read_command_line_arguments(report_t& report,
|
strings_list read_command_arguments(report_t& report, strings_list args);
|
||||||
int argc, char * argv[]);
|
|
||||||
void normalize_session_options(session_t& session);
|
void normalize_session_options(session_t& session);
|
||||||
function_t look_for_precommand(report_t& report, const string& verb);
|
function_t look_for_precommand(report_t& report, const string& verb);
|
||||||
journal_t * read_journal_files(session_t& session, const string& account);
|
journal_t * read_journal_files(session_t& session, const string& account);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue