From a435b4c4f6c02839b0f366f6fc7c06609d315e25 Mon Sep 17 00:00:00 2001 From: Geert Janssens Date: Thu, 28 May 2020 22:24:18 +0200 Subject: [PATCH] Port command line option handling to boost::program_options This allows for better separation of options in common, gnucash and gnucash-cli options. --- CMakeLists.txt | 6 +- gnucash/gnucash-cli.cpp | 69 +++++++++++++-- gnucash/gnucash-core-app.cpp | 157 +++++++++++++---------------------- gnucash/gnucash-core-app.hpp | 15 +++- gnucash/gnucash.cpp | 73 ++++++++++++---- 5 files changed, 192 insertions(+), 128 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c70b0d44ee..2efbe94755 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -517,13 +517,13 @@ set (Boost_FIND_QUIETLY ON) if (NOT DEFINED ${BOOST_ROOT}) set(BOOST_ROOT $ENV{BOOST_ROOT}) endif() -find_package (Boost 1.67.0 COMPONENTS date_time regex locale filesystem system) +find_package (Boost 1.67.0 COMPONENTS date_time regex locale filesystem system program_options) if (Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) set(HAVE_BOOST 1) else() - find_package (Boost 1.60.0 REQUIRED COMPONENTS date_time regex locale filesystem system) + find_package (Boost 1.60.0 REQUIRED COMPONENTS date_time regex locale filesystem system program_options) if (Boost_FOUND) include (CheckIncludeFileCXX) set(CMAKE_REQUIRED_FLAGS "-std=c++17") @@ -538,7 +538,7 @@ else() endif() endif() if (NOT HAVE_BOOST) -message (SEND_ERROR "A suitable Boost is not installed, and is required. GnuCash requires that Boost be compatible and compiled with C++17. Boost 1.67 is the first compatible release but some distributions have patched earlier ones to work with C++17. Please install it and ensure that the following libraries are built: date_time, filesystem, locale, and regex.") +message (SEND_ERROR "A suitable Boost is not installed, and is required. GnuCash requires that Boost be compatible and compiled with C++17. Boost 1.67 is the first compatible release but some distributions have patched earlier ones to work with C++17. Please install it and ensure that the following libraries are built: date_time, filesystem, locale, regex, program_options and system.") endif() diff --git a/gnucash/gnucash-cli.cpp b/gnucash/gnucash-cli.cpp index 767c971052..afd59819b9 100644 --- a/gnucash/gnucash-cli.cpp +++ b/gnucash/gnucash-cli.cpp @@ -33,6 +33,7 @@ extern "C" { #include +#include #include #include #include @@ -107,17 +108,71 @@ fail: gnc_shutdown(1); } +namespace Gnucash { + + class GnucashCli : public CoreApp + { + public: + GnucashCli (const char* app_name); + void parse_command_line (int *argc, char ***argv); + void start (int argc, char **argv); + private: + void configure_program_options (void); + + std::string m_quotes_file; + }; + +} + +Gnucash::GnucashCli::GnucashCli (const char *app_name) : Gnucash::CoreApp (app_name) +{ + configure_program_options(); +} + +void +Gnucash::GnucashCli::parse_command_line (int *argc, char ***argv) +{ + Gnucash::CoreApp::parse_command_line (argc, argv); + + if (m_opt_map.count ("namespace")) + gnc_prefs_set_namespace_regexp(m_opt_map["namespace"]. + as().c_str()); + + if (m_opt_map.count ("add-price-quotes")) + m_quotes_file = m_opt_map["add-price-quotes"].as(); +} + +// Define command line options specific to gnucash-cli. +void +Gnucash::GnucashCli::configure_program_options (void) +{ + bpo::options_description quotes_options(_("Price Retrieval Options")); + quotes_options.add_options() + ("add-price-quotes", bpo::value(), + N_("Add price quotes to given GnuCash datafile.\n")) + ("namespace", bpo::value(), + N_("Regular expression determining which namespace commodities will be retrieved")); + + m_opt_desc->add (quotes_options); +} + +void +Gnucash::GnucashCli::start (int argc, char **argv) +{ + Gnucash::CoreApp::start(); + + if (not m_quotes_file.empty()) + scm_boot_guile (argc, argv, inner_main_add_price_quotes, (void *)m_quotes_file.c_str()); + +} + int -main(int argc, char ** argv) +main(int argc, char **argv) { - Gnucash::CoreApp application; + Gnucash::GnucashCli application (argv[0]); application.parse_command_line (&argc, &argv); - application.start (); - - auto quotes_file = application.get_quotes_file (); - if (quotes_file) - scm_boot_guile (argc, argv, inner_main_add_price_quotes, (void *)quotes_file); + application.start (argc, argv); exit(0); /* never reached */ } diff --git a/gnucash/gnucash-core-app.cpp b/gnucash/gnucash-core-app.cpp index 576f9dfb3e..3ed3f096e8 100644 --- a/gnucash/gnucash-core-app.cpp +++ b/gnucash/gnucash-core-app.cpp @@ -49,6 +49,8 @@ extern "C" { #include #include +#include +#include namespace bl = boost::locale; @@ -542,11 +544,22 @@ Gnucash::CoreApp::CoreApp () textdomain(PROJECT_NAME); bind_textdomain_codeset(PROJECT_NAME, "UTF-8"); g_free(localedir); +} + +Gnucash::CoreApp::CoreApp (const char* app_name) +{ + + CoreApp(); + + m_app_name = std::string(app_name); // Now that gettext is properly initialized, set our help tagline. tagline = bl::translate("- GnuCash, accounting for personal and small business finance").str(gnc_get_locale()); + m_opt_desc = std::make_unique ((bl::format (bl::gettext ("{1} [options]")) % m_app_name).str() + tagline); + add_common_program_options(); } + /* Parse command line options, using GOption interface. * We can't let gtk_init_with_args do it because it fails * before parsing any arguments if the GUI can't be initialized. @@ -560,93 +573,9 @@ Gnucash::CoreApp::parse_command_line (int *argc, char ***argv) char *tmp_log_to_filename = NULL; #endif - GOptionEntry options[] = - { - { - "version", 'v', 0, G_OPTION_ARG_NONE, &gnucash_show_version, - N_("Show GnuCash version"), NULL - }, - - { - "debug", '\0', 0, G_OPTION_ARG_NONE, &debugging, - N_("Enable debugging mode: provide deep detail in the logs.\nThis is equivalent to: --log \"=info\" --log \"qof=info\" --log \"gnc=info\""), NULL - }, - - { - "extra", '\0', 0, G_OPTION_ARG_NONE, &extra, - N_("Enable extra/development/debugging features."), NULL - }, - - { - "log", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &log_flags, - N_("Log level overrides, of the form \"modulename={debug,info,warn,crit,error}\"\nExamples: \"--log qof=debug\" or \"--log gnc.backend.file.sx=info\"\nThis can be invoked multiple times."), - NULL - }, - - { - "logto", '\0', 0, G_OPTION_ARG_STRING, &tmp_log_to_filename, - N_("File to log into; defaults to \"/tmp/gnucash.trace\"; can be \"stderr\" or \"stdout\"."), - NULL - }, - - { - "nofile", '\0', 0, G_OPTION_ARG_NONE, &nofile, - N_("Do not load the last file opened"), NULL - }, - { - "gsettings-prefix", '\0', 0, G_OPTION_ARG_STRING, &gsettings_prefix, - N_("Set the prefix for gsettings schemas for gsettings queries. This can be useful to have a different settings tree while debugging."), - /* Translators: Argument description for autohelp; see - * http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("GSETTINGSPREFIX") - }, - { - "add-price-quotes", '\0', 0, G_OPTION_ARG_STRING, &add_quotes_file, - N_("Add price quotes to given GnuCash datafile"), - /* Translators: Argument description for autohelp; see - * http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("FILE") - }, - { - "namespace", '\0', 0, G_OPTION_ARG_STRING, &namespace_regexp, - N_("Regular expression determining which namespace commodities will be retrieved"), - /* Translators: Argument description for autohelp; see - * http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("REGEXP") - }, - { - G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args_remaining, NULL, N_("[datafile]") - - }, - { NULL } - }; - - GError *error = NULL; - GOptionContext *context = g_option_context_new (tagline.c_str()); - - g_option_context_add_main_entries (context, options, PROJECT_NAME); - if (!gtk_help_msg.empty()) - { - GOptionEntry gtk_help_options[] = - { - { - "help-gtk", 'v', 0, G_OPTION_ARG_NONE, >k_show_help, - N_("Show GTK+ Options"), NULL - }, - { NULL } - }; - g_option_group_add_entries (g_option_context_get_main_group (context), gtk_help_options); - } - - if (!g_option_context_parse (context, argc, argv, &error)) - { - std::cerr << error->message << "\n" - << bl::format (bl::translate ("Run '{1} --help' to see a full list of available command line options.")) % *argv[0] - << "\n"; - g_error_free (error); - exit (1); - } - g_option_context_free (context); + bpo::store (bpo::command_line_parser (*argc, *argv). + options (*m_opt_desc.get()).positional(m_pos_opt_desc).run(), m_opt_map); + bpo::notify (m_opt_map); if (tmp_log_to_filename != NULL) { @@ -658,7 +587,7 @@ Gnucash::CoreApp::parse_command_line (int *argc, char ***argv) #endif } - if (gnucash_show_version) + if (m_opt_map.count ("version")) { bl::format rel_fmt (bl::translate ("GnuCash {1}")); bl::format dev_fmt (bl::translate ("GnuCash {1} development version")); @@ -672,19 +601,53 @@ Gnucash::CoreApp::parse_command_line (int *argc, char ***argv) exit(0); } - gnc_prefs_set_debugging(debugging); - gnc_prefs_set_extra(extra); + if (m_opt_map.count ("help")) + { + std::cout << *m_opt_desc.get() << "\n"; + exit(0); + } - if (gsettings_prefix) - gnc_gsettings_set_prefix(g_strdup(gsettings_prefix)); + gnc_prefs_set_debugging (m_opt_map.count ("debug")); + gnc_prefs_set_extra (m_opt_map.count ("extra")); - if (namespace_regexp) - gnc_prefs_set_namespace_regexp(namespace_regexp); + if (m_opt_map.count ("gsettings-prefix")) + gnc_gsettings_set_prefix (m_opt_map["gsettings-prefix"]. + as().c_str()); if (args_remaining) file_to_load = args_remaining[0]; } +/* Define command line options common to all gnucash binaries. */ +void +Gnucash::CoreApp::add_common_program_options (void) +{ + #ifdef __MINGW64__ + wchar_t *tmp_log_to_filename = NULL; + #else + char *tmp_log_to_filename = NULL; + #endif + + bpo::options_description common_options(_("Common Options")); + common_options.add_options() + ("help,h", + N_("Show this help message")) + ("version,v", + N_("Show GnuCash version")) + ("debug", + N_("Enable debugging mode: provide deep detail in the logs.\nThis is equivalent to: --log \"=info\" --log \"qof=info\" --log \"gnc=info\"")) + ("extra", + N_("Enable extra/development/debugging features.")) + ("log", bpo::value< std::vector >(), + N_("Log level overrides, of the form \"modulename={debug,info,warn,crit,error}\"\nExamples: \"--log qof=debug\" or \"--log gnc.backend.file.sx=info\"\nThis can be invoked multiple times.")) + ("logto", bpo::value(), + N_("File to log into; defaults to \"/tmp/gnucash.trace\"; can be \"stderr\" or \"stdout\".")) + ("gsettings-prefix", bpo::value(), + N_("Set the prefix for gsettings schemas for gsettings queries. This can be useful to have a different settings tree while debugging.")); + + m_opt_desc->add (common_options); +} + const char* Gnucash::CoreApp::get_file_to_load (void) { @@ -697,12 +660,6 @@ Gnucash::CoreApp::get_no_file (void) return nofile; } -const char* -Gnucash::CoreApp::get_quotes_file (void) -{ - return add_quotes_file; -} - void Gnucash::CoreApp::start (void) { diff --git a/gnucash/gnucash-core-app.hpp b/gnucash/gnucash-core-app.hpp index 449257e8ff..a93a1900cd 100644 --- a/gnucash/gnucash-core-app.hpp +++ b/gnucash/gnucash-core-app.hpp @@ -24,34 +24,43 @@ #ifndef GNUCASH_CORE_APP_HPP #define GNUCASH_CORE_APP_HPP +#include #include +#include namespace Gnucash { +namespace bpo = boost::program_options; + class CoreApp { public: CoreApp (); + CoreApp (const char* app_name); void parse_command_line (int *argc, char ***argv); void start (void); const char *get_file_to_load (void); int get_no_file (void); - const char *get_quotes_file (void); protected: - std::string gtk_help_msg; int gtk_show_help = 0; + std::string m_app_name; std::string tagline; + std::unique_ptr m_opt_desc; + bpo::variables_map m_opt_map; + bpo::positional_options_description m_pos_opt_desc; + private: + void add_common_program_options (void); /* Command-line option variables */ int gnucash_show_version = 0; int debugging = 0; int extra = 0; - char **log_flags = nullptr; + char **log_flags; char *log_to_filename = nullptr; int nofile = 0; const char *gsettings_prefix = nullptr; diff --git a/gnucash/gnucash.cpp b/gnucash/gnucash.cpp index 0f7dfb0e09..f3db65996f 100644 --- a/gnucash/gnucash.cpp +++ b/gnucash/gnucash.cpp @@ -356,41 +356,84 @@ namespace Gnucash { class Gnucash : public CoreApp { public: + Gnucash (const char* app_name); void parse_command_line (int *argc, char ***argv); + + std::string get_quotes_file (void) + { return m_quotes_file; } + private: + void configure_program_options (void); + + std::string m_gtk_help_msg; + std::string m_quotes_file; // Deprecated will be removed in gnucash 5.0 }; } +Gnucash::Gnucash::Gnucash (const char *app_name) : Gnucash::CoreApp (app_name) +{ + configure_program_options(); +} + + void Gnucash::Gnucash::parse_command_line (int *argc, char ***argv) +{ + Gnucash::CoreApp::parse_command_line (argc, argv); + + if (m_opt_map.count ("help-gtk")) + { + std::cout << m_gtk_help_msg; + exit(0); + } + + if (m_opt_map.count ("namespace")) + gnc_prefs_set_namespace_regexp (m_opt_map["namespace"]. + as().c_str()); + + if (m_opt_map.count ("add-price-quotes")) + m_quotes_file = m_opt_map["add-price-quotes"].as(); +} + +// Define command line options specific to gnucash. +void +Gnucash::Gnucash::configure_program_options (void) { // The g_option context dance below is done to be able to show a help message // for gtk's options. The options themselves are already parsed out by // gtk_init_check by the time this function is called though. So it really only // serves to be able to display a help message. - GError *error = NULL; auto context = g_option_context_new (tagline.c_str()); auto gtk_options = gtk_get_option_group(FALSE); g_option_context_add_group (context, gtk_options); - gtk_help_msg = std::string (g_option_context_get_help (context, FALSE, gtk_options)); + m_gtk_help_msg = g_option_context_get_help (context, FALSE, gtk_options); g_option_context_free (context); - // Pass remaining command line bits to our base class for further parsing - // This will include a --help-gtk option to display gtk's option help - // extracted above - Gnucash::CoreApp::parse_command_line (argc, argv); - - if (gtk_show_help) - { - std::cout << gtk_help_msg; - exit(0); - } + bpo::options_description app_options(_("Application Options")); + app_options.add_options() + ("nofile", + N_("Do not load the last file opened")) + ("help-gtk", _("Show help for gtk options")) + ("add-price-quotes", bpo::value(), + N_("Add price quotes to given GnuCash datafile.\n" + "Note this option has been deprecated and will be removed in GnuCash 5.0.\n" + "Please use \"gnucash-cli --add-price-quotes\" instead.")) + ("namespace", bpo::value(), + N_("Regular expression determining which namespace commodities will be retrieved" + "Note this option has been deprecated and will be removed in GnuCash 5.0.\n" + "Please use \"gnucash-cli --add-price-quotes\" instead.")) + ("input-file", bpo::value(), + N_("[datafile]")); + + m_pos_opt_desc.add("input-file", -1); + + m_opt_desc->add (app_options); } int main(int argc, char ** argv) { - Gnucash::Gnucash application; + Gnucash::Gnucash application (argv[0]); /* We need to initialize gtk before looking up all modules */ if(!gtk_init_check (&argc, &argv)) @@ -407,9 +450,9 @@ main(int argc, char ** argv) /* If asked via a command line parameter, fetch quotes only */ auto quotes_file = application.get_quotes_file (); - if (quotes_file) + if (!quotes_file.empty()) { - scm_boot_guile (argc, argv, inner_main_add_price_quotes, (void*) quotes_file); + scm_boot_guile (argc, argv, inner_main_add_price_quotes, (void*) quotes_file.c_str()); exit (0); /* never reached */ }