diff options
author | John Wiegley <johnw@newartisans.com> | 2009-02-04 18:23:18 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-02-04 19:55:08 -0400 |
commit | 73cf3b01fbd50c3a8a4fd96ff69643c28394d8fe (patch) | |
tree | 695fde78e03351750210715ea76ec686ff04fbfc /src | |
parent | b9603a1512acdfeb5d304e5ae910c1da553b3337 (diff) | |
download | fork-ledger-73cf3b01fbd50c3a8a4fd96ff69643c28394d8fe.tar.gz fork-ledger-73cf3b01fbd50c3a8a4fd96ff69643c28394d8fe.tar.bz2 fork-ledger-73cf3b01fbd50c3a8a4fd96ff69643c28394d8fe.zip |
Added structural support in main() for using a REPL.
Diffstat (limited to 'src')
-rw-r--r-- | src/main.cc | 261 | ||||
-rw-r--r-- | src/option.cc | 35 | ||||
-rw-r--r-- | src/option.h | 3 | ||||
-rw-r--r-- | src/system.hh | 4 | ||||
-rw-r--r-- | src/work.cc | 13 | ||||
-rw-r--r-- | src/work.h | 3 |
6 files changed, 218 insertions, 101 deletions
diff --git a/src/main.cc b/src/main.cc index b5b65da9..3cdc6cf4 100644 --- a/src/main.cc +++ b/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; + + for (s = string; isspace (*s); s++) + ; + + if (*s == 0) + return (s); + + t = s + strlen (s) - 1; + while (t > s && isspace (*t)) + t--; + *++t = '\0'; - INFO("Ledger starting"); + return s; + } + + strings_list split_arguments(char * line) + { + strings_list args; - // Initialize global Boost/C++ environment - std::ios::sync_with_stdio(false); - filesystem::path::default_name_check(filesystem::portable_posix_name); + // 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); - // 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); + 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 - - // 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 (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 { - 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 + + // 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; - // 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; + 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; } +} + +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 - // Close the output stream, waiting on the pager process to exit if need be - report->output_stream.close(); + 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(); diff --git a/src/option.cc b/src/option.cc index a0891c4c..7f8bdeb4 100644 --- a/src/option.cc +++ b/src/option.cc @@ -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 diff --git a/src/option.h b/src/option.h index acba035c..14b85c50 100644 --- a/src/option.h +++ b/src/option.h @@ -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); diff --git a/src/system.hh b/src/system.hh index 30a1e8d0..f70ee57b 100644 --- a/src/system.hh +++ b/src/system.hh @@ -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> diff --git a/src/work.cc b/src/work.cc index a54b1352..9bd75c9c 100644 --- a/src/work.cc +++ b/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); |