diff --git a/gnucash/gnucash-cli.cpp b/gnucash/gnucash-cli.cpp index 23a8475fc9d..d64bf361916 100644 --- a/gnucash/gnucash-cli.cpp +++ b/gnucash/gnucash-cli.cpp @@ -33,6 +33,9 @@ #include #include #include +#include "gnc-session.h" +#include "gnc-prefs-utils.h" +#include "libguile.h" #include #include @@ -40,6 +43,7 @@ #include #endif #include +#include #include namespace bl = boost::locale; @@ -62,6 +66,11 @@ namespace Gnucash { boost::optional m_namespace; bool m_verbose = false; + boost::optional m_script; + boost::optional m_language; + bool m_interactive; + bool m_open_readwrite; + boost::optional m_report_cmd; boost::optional m_report_name; boost::optional m_export_type; @@ -107,6 +116,15 @@ Gnucash::GnucashCli::configure_program_options (void) m_opt_desc_display->add (quotes_options); m_opt_desc_all.add (quotes_options); + bpo::options_description cli_options(_("Scripting and Interactive Session Options")); + cli_options.add_options() + ("script,S", bpo::value (&m_script), "Script to run") + ("interactive,I", bpo::bool_switch (&m_interactive), "Interactive session after possibly running script") + ("language,L", bpo::value (&m_language), "Specify language for script or interactive session; guile (default) or python") + ("readwrite,W", bpo::bool_switch (&m_open_readwrite), "Open datafile read-write for script and/or interactive session"); + m_opt_desc_display->add (cli_options); + m_opt_desc_all.add (cli_options); + bpo::options_description report_options(_("Report Generation Options")); report_options.add_options() ("report,R", bpo::value (&m_report_cmd), @@ -126,11 +144,117 @@ may be specified to describe some saved options.\n" } +struct cli_struct +{ + boost::optional script; + bool interactive; + bool verbose; +}; + +static void +run_guile_cli (void *data, [[maybe_unused]] int argc, [[maybe_unused]] char **argv) +{ + auto args = static_cast(data); + scm_c_use_module ("system repl repl"); + scm_c_use_module ("gnucash core-utils"); + scm_c_use_module ("gnucash engine"); + scm_c_use_module ("gnucash app-utils"); + scm_c_use_module ("gnucash report"); + scm_c_use_module ("ice-9 readline"); + scm_c_eval_string ("(activate-readline)"); + if (args->script) + { + if (args->verbose) + std::cout << "Running script from " << *args->script << "..."; + scm_c_primitive_load (args->script->c_str()); + if (args->verbose) + std::cout << "success!" << std::endl; + } + if (args->interactive) + { + if (args->verbose) + std::cout << "Starting CLI... " << std::endl; + scm_c_eval_string ("(start-repl)"); + } + if (gnc_current_session_exist()) + { + if (args->verbose) + std::cout << "Warning: session still open. Clearing session..." << std::endl; + gnc_clear_current_session (); + } +} + +static void load_file (std::string m_file_to_load, bool m_open_readwrite, bool m_verbose) +{ + if (m_verbose) + std::cout << "\n\nLoading " << m_file_to_load + << (m_open_readwrite ? " (r/w)" : " (readonly)"); + auto session = gnc_get_current_session(); + auto mode = m_open_readwrite ? SESSION_READ_ONLY : SESSION_NORMAL_OPEN; + while (true) + { + qof_session_begin (session, m_file_to_load.c_str(), mode); + auto io_err = qof_session_get_error (session); + switch (io_err) + { + case ERR_BACKEND_NO_ERR: + qof_session_load (session, NULL); + if (m_verbose) + std::cout << "... done!\n\n"; + return; + case ERR_BACKEND_LOCKED: + case ERR_BACKEND_READONLY: + if (m_verbose) + std::cout << " (forced readonly)"; + mode = SESSION_READ_ONLY; + break; + default: + if (m_verbose) + std::cout << "... unknown error. Abort." << std::endl; + exit (1); + } + } +} + int Gnucash::GnucashCli::start ([[maybe_unused]] int argc, [[maybe_unused]] char **argv) { Gnucash::CoreApp::start(); + if (m_interactive || m_script) + { + if (m_open_readwrite && !m_file_to_load) + { + std::cerr << "missing datafile to be open read/write!" << std::endl; + return 1; + } + if (m_script && (!std::filesystem::exists (*m_script) || + std::filesystem::is_directory (*m_script))) + { + std::cerr << "invalid script " << *m_script << std::endl; + return 1; + } + gnc_prefs_init (); + cli_struct args = { m_script, m_interactive, m_verbose }; + if (!m_language || *m_language == "guile") + { + if (m_file_to_load) + load_file (*m_file_to_load, m_open_readwrite, m_verbose); + scm_boot_guile (0, nullptr, run_guile_cli, &args); + return 0; + } + else if (*m_language == "python") + { + std::cerr << "don't know how to launch python" << std::endl; + return 1; + } + else + { + std::cerr << "Valid languages are 'python' or 'guile'" << std::endl; + return 1; + } + } + if (!m_quotes_cmd.empty()) { if (m_quotes_cmd.front() == "info")