From c72b2de3794c7c5100cc8e007b3c199a2f237fe6 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Tue, 22 Oct 2019 10:35:37 +0300 Subject: Use github.com/Taywee/args to parse command line - This adds 3rd-party/args/args.git subrepository --- .gitmodules | 3 + 3rd-party/args/args.git | 1 + 3rd-party/args/meson.build | 5 ++ lib/configuration/commandline.cpp | 59 ------------------- lib/configuration/commandline.h | 79 ------------------------- lib/configuration/meson.build | 2 +- linux/makepkg/PKGBUILD | 6 +- meson.build | 1 + src/builtins.cpp | 27 --------- src/builtins.h | 5 -- src/main.cpp | 120 ++++++++++++++++++++++++++++---------- src/meson.build | 2 +- 12 files changed, 107 insertions(+), 203 deletions(-) create mode 160000 3rd-party/args/args.git create mode 100644 3rd-party/args/meson.build delete mode 100644 lib/configuration/commandline.cpp delete mode 100644 lib/configuration/commandline.h diff --git a/.gitmodules b/.gitmodules index 1c11e7c..1695827 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "3rd-party/SingleApplication/SingleApplication.git"] path = 3rd-party/SingleApplication/SingleApplication.git url = https://github.com/itay-grudev/SingleApplication.git +[submodule "3rd-party/args/args.git"] + path = 3rd-party/args/args.git + url = https://github.com/Taywee/args diff --git a/3rd-party/args/args.git b/3rd-party/args/args.git new file mode 160000 index 0000000..78e27fa --- /dev/null +++ b/3rd-party/args/args.git @@ -0,0 +1 @@ +Subproject commit 78e27faf75ff7d20f232f11ffcef65cde43c449d diff --git a/3rd-party/args/meson.build b/3rd-party/args/meson.build new file mode 100644 index 0000000..c4ef5dc --- /dev/null +++ b/3rd-party/args/meson.build @@ -0,0 +1,5 @@ +# This is a header-only lib, all we need to do is include it +dep_args = declare_dependency( + include_directories: include_directories('args.git') +) + diff --git a/lib/configuration/commandline.cpp b/lib/configuration/commandline.cpp deleted file mode 100644 index 4581c84..0000000 --- a/lib/configuration/commandline.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "commandline.h" -#include "config.h" -#include - -namespace po = boost::program_options; - -inline std::string defaultUserConfigLocation() -{ -#ifdef CONFIG_PATH_CONFIG - return CONFIG_PATH_CONFIG; -#else - // try to locate an existing config - QString path = QStandardPaths::locate(QStandardPaths::ConfigLocation, "smolbote/smolbote.cfg"); - - // it's possible there is no config, so set the path properly - if(path.isEmpty()) - path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/smolbote/smolbote.cfg"; - - return path.toStdString(); -#endif -} - -CommandLine::CommandLine(int argc, char **argv) - : m_homePath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString()) -{ - m_description.add_options() - ("help,h", "Display command-line options list.") - ("version,v", "Display version information.") - ("build", "Display build commit.") - - ("config,c", po::value()->default_value(defaultUserConfigLocation()), "Set the configuration file.") - ("no-remote", "Do not accept or send remote commands.") - - ("session,s", po::value(), "Open the selected session.") - ("pick-session", "Show all available sessions and select which one to open.") - - ("args", po::value>(), "Command(s) and/or URL(s).") - ; - - m_arguments.add("args", -1); - - try { - auto cmd = po::command_line_parser(argc, argv); - cmd.allow_unregistered(); - cmd.options(m_description); - cmd.positional(m_arguments); - po::store(cmd.run(), vm); - } catch(const po::error &e) { - qWarning("Error parsing cmd: %s", e.what()); - } -} diff --git a/lib/configuration/commandline.h b/lib/configuration/commandline.h deleted file mode 100644 index 3c4dd81..0000000 --- a/lib/configuration/commandline.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_COMMANDLINE_H -#define SMOLBOTE_COMMANDLINE_H - -#include -#include -#include - -class CommandLine -{ -public: - CommandLine(int argc, char **argv); - - bool exists(const char *path) const - { - return (vm.count(path) > 0); - } - - template - std::optional value(const char *path) const - { - if(vm.count(path) == 0) { - return std::nullopt; - } - - if constexpr(std::is_same_v) { - return std::optional(QString::fromStdString(this->value(path).value())); - //return std::optional(vm[path].as()); - - } else if constexpr(std::is_same_v) { - QStringList r; - for(const std::string &item : this->value>(path).value()) { - r.append(QString::fromStdString(item)); - } - return std::optional(r); - - } else if constexpr(std::is_same_v) { - - if(vm[path].value().type() == typeid(int)) { - return std::optional(std::to_string(vm[path].as())); - } - - if(vm[path].value().type() == typeid(bool)) { - return std::optional(vm[path].as() ? "true" : "false"); - } - - std::string r = vm[path].as(); - - // check if it's a path - if(r.front() == '~') { - r.replace(0, 1, m_homePath); - } - - return std::optional(r); - - } else - return std::optional(vm[path].as()); - } - - const boost::program_options::options_description& description() const - { - return m_description; - } - -private: - const std::string m_homePath; - boost::program_options::options_description m_description; - boost::program_options::positional_options_description m_arguments; - boost::program_options::variables_map vm; -}; - -#endif // SMOLBOTE_COMMANDLINE_H diff --git a/lib/configuration/meson.build b/lib/configuration/meson.build index 57bafbc..19f57f4 100644 --- a/lib/configuration/meson.build +++ b/lib/configuration/meson.build @@ -3,7 +3,7 @@ configuration_moc = mod_qt5.preprocess( dependencies: dep_qt5 ) -configuration_lib = static_library('configuration', ['commandline.cpp', 'configuration.cpp', configuration_moc], +configuration_lib = static_library('configuration', ['configuration.cpp', configuration_moc], dependencies: [dep_boost, dep_qt5, autogen_config] ) diff --git a/linux/makepkg/PKGBUILD b/linux/makepkg/PKGBUILD index 9a4d13a..4fed183 100644 --- a/linux/makepkg/PKGBUILD +++ b/linux/makepkg/PKGBUILD @@ -18,9 +18,11 @@ makedepends=('git' 'meson' 'boost' 'python-kconfiglib' 'openssl' 'qt5-tools' 'sc # this is the central repository source=("git+https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote.git" - "git+https://github.com/itay-grudev/SingleApplication.git") + "git+https://github.com/itay-grudev/SingleApplication.git" + "git+https://github.com/Taywee/args") sha512sums=('SKIP' + 'SKIP' 'SKIP') #validgpgkeys=(# Aqua-sama @@ -42,6 +44,8 @@ prepare() { git submodule init git config submodule.3rd-party/SingleApplication/SingleApplication.git.url $srcdir/SingleApplication git submodule update 3rd-party/SingleApplication/SingleApplication.git + git config submodule.3rd-party/args/args.git.url $srcdir/args + git submodule update 3rd-party/args/args.git } pkgver() { diff --git a/meson.build b/meson.build index 6412730..af2dd63 100644 --- a/meson.build +++ b/meson.build @@ -81,6 +81,7 @@ subdir('lib/urlfilter') subdir('lib/webprofile') subdir('3rd-party/SingleApplication') +subdir('3rd-party/args') subdir('src') subdir('lang') diff --git a/src/builtins.cpp b/src/builtins.cpp index 55bdc31..1af2dc8 100644 --- a/src/builtins.cpp +++ b/src/builtins.cpp @@ -34,30 +34,3 @@ int builtins::build() std::cout << poi_Version << std::endl; return 0; } - -int builtins::help(const char *cmd, - const boost::program_options::options_description &cmd_opts, - const boost::program_options::options_description &config_opts, - const CommandHash_t &pluginCommands, const QTranslator *translator) -{ - const auto version = QVersionNumber::fromString(QLatin1String(poi_Version)).toString().toStdString(); - std::cout << tr(translator, "smolbote ") << version << tr(translator, ": yet another no-frills browser\n"); - std::cout << tr(translator, "Usage: ") << cmd << tr(translator, " [options] [command/URL(s)]\n\n"); - - std::cout << tr(translator, "Command-line Options:\n") << cmd_opts << '\n'; - - std::cout << tr(translator, "Commands: \n"); - for(auto it = pluginCommands.constBegin(); it != pluginCommands.constEnd(); ++it) - std::cout << " " << it.key().toStdString() << '\n'; - std::cout << '\n'; - - std::cout << tr(translator, "Configuration Options:\n") << config_opts << '\n'; - -#ifdef Q_OS_UNIX - std::cout << tr(translator, "For more information on usage, refer to the manual page smolbote.7\n"); - std::cout << tr(translator, "For more information on configuration, refer to the manual page smolbote.5\n"); -#endif - - std::cout << std::endl; - return 0; -} diff --git a/src/builtins.h b/src/builtins.h index e151e8d..a3b9b07 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -16,11 +16,6 @@ namespace builtins { int version(); int build(); -int help(const char *cmd, - const boost::program_options::options_description &cmd_opts, - const boost::program_options::options_description &config_opts, - const CommandHash_t &pluginCommands, - const QTranslator *translator); } #endif diff --git a/src/main.cpp b/src/main.cpp index 8063b86..02a1168 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,22 +8,57 @@ #include "browser.h" #include "builtins.h" -#include "commandline.h" #include "configuration.h" +#include "crashhandler.h" #include "session/session.h" #include "session/sessiondialog.h" #include "util.h" -#include "crashhandler.h" #include "version.h" #include #include #include -#include #include +#include +#include #include #include +#include #include +typedef std::function::const_iterator, std::vector::const_iterator)> subcommand_func; +typedef std::unordered_map command_map; + +// a helper function to join the keys of a command_map into a string +inline std::string join_keys(const command_map &map, const std::string sep = ", ") +{ + std::vector keys(map.size()); + std::transform(map.begin(), map.end(), keys.begin(), [](auto pair) { return pair.first; }); + std::sort(keys.begin(), keys.end()); + + std::string k; + std::for_each(keys.begin(), keys.end() - 1, [&k, &sep](const std::string &piece) { k += piece + sep; }); + k += keys.back(); + + return k; +} + +#include +inline std::string defaultUserConfigLocation() +{ +#ifdef CONFIG_PATH_CONFIG + return CONFIG_PATH_CONFIG; +#else + // try to locate an existing config + QString path = QStandardPaths::locate(QStandardPaths::ConfigLocation, "smolbote/smolbote.cfg"); + + // it's possible there is no config, so set the path properly + if(path.isEmpty()) + path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/smolbote/smolbote.cfg"; + + return path.toStdString(); +#endif +} + #include "config.h" #if defined(CONFIG_USEPLASMA) && !defined(PLASMA) #error "You have enabled Plasma integration, but Frameworks was not found." @@ -41,15 +76,40 @@ int main(int argc, char **argv) spdlog::set_level(spdlog::level::debug); // Set global log level to debug #endif - const std::unique_ptr cmd = std::make_unique(argc, argv); - // --version - if(cmd->exists("version")) { - return builtins::version(); - } + const std::vector args(argv + 1, argv + argc); + args::ArgumentParser parser("smolbote: yet another no-frills browser", "For more information on usage, refer to the manual page."); + parser.Prog(argv[0]); + + args::HelpFlag cmd_help(parser, "help", "Display this help message.", { 'h', "help" }); + args::Flag cmd_version(parser, "version", "Display version information.", { 'v', "version" }); + args::Flag cmd_build(parser, "build", "Display build commit.", { 'b', "build" }); + + args::ValueFlag cmd_config(parser, "config", "Set the configuration file.", { 'c', "config" }); + + args::Flag cmd_noRemote(parser, "no-remote", "Do not accept or send remote commands.", { "no-remote" }); + + args::Flag cmd_pickSession(parser, "pick-session", "Show all available sessions and select which one to open", { "pick-session" }); + args::ValueFlag cmd_session(parser, "session", "Open the specified session.", { 's', "session" }); + + args::PositionalList cmd_args(parser, "URL(s)", "List of URLs to open"); + + try { + auto next = parser.ParseArgs(args); + + if(cmd_version) + return builtins::version(); + if(cmd_build) + return builtins::build(); - // --build - if(cmd->exists("build")) { - return builtins::build(); + } catch(args::Help) { + std::cout << parser; + //std::cout << "Available subcommands: " << command_keys << std::endl; + return 0; + + } catch(args::Error e) { + std::cerr << e.what() << std::endl; + std::cerr << parser; + return -1; } // Disable Chromium's crash handler so breakpad can capture crashes instead. @@ -60,7 +120,13 @@ int main(int argc, char **argv) } // create and load configuration - std::unique_ptr config = std::make_unique(argc, argv, cmd->value("config").value()); + const std::string config_path = [&]() { + if(cmd_config) + return args::get(cmd_config); + else + return defaultUserConfigLocation(); + }(); + std::unique_ptr config = std::make_unique(argc, argv, config_path); QTranslator translator; if(config->exists("browser.translation")) { translator.load(config->value("browser.translation").value()); @@ -70,7 +136,7 @@ int main(int argc, char **argv) CommandHash_t pluginCommands; // Load plugins - for(const QString &path : Util::files(config->value("plugins.path").value(), {"*.so", "*.dll"})) { + for(const QString &path : Util::files(config->value("plugins.path").value(), { "*.so", "*.dll" })) { auto *loader = new PluginLoader(path); const bool loaded = loader->load(); spdlog::info("{} plugin {}", loaded ? "Loaded" : "Failed to load", qUtf8Printable(path)); @@ -85,10 +151,6 @@ int main(int argc, char **argv) } } - if(cmd->exists("help")) { - return builtins::help(argv[0], cmd->description(), config->description(), pluginCommands, &translator); - } - // argc, argv, allowSecondary Browser app(argc, argv); // set this, otherwise the webview becomes black when using a stylesheet @@ -129,27 +191,25 @@ int main(int argc, char **argv) #endif #endif // CONFIG_USEBREAKPAD - const bool isStandalone = cmd->exists("no-remote"); const auto profile = config->value("profile.default"); app.setConfiguration(config); // app takes ownership of config app.setup(plugins); QStringList urls; - if(const auto arguments = cmd->value>("args")) { - for(const auto &u : arguments.value()) { - if(pluginCommands.contains(QString::fromStdString(u))) { - return pluginCommands.value(QString::fromStdString(u))(); - } else { - urls.append(QString::fromStdString(u)); - } + + for(const auto &u : args::get(cmd_args)) { + if(pluginCommands.contains(QString::fromStdString(u))) { + return pluginCommands.value(QString::fromStdString(u))(); + } else { + urls.append(QString::fromStdString(u)); } } if(urls.isEmpty()) urls.append(QString()); // if app is primary, create new sessions from received messages - if(app.isPrimary() && !isStandalone) { + if(app.isPrimary() && !cmd_noRemote) { QObject::connect(&app, &Browser::receivedMessage, &app, [](quint32 instanceId, QByteArray message) { Q_UNUSED(instanceId); auto doc = QJsonDocument::fromJson(message); @@ -160,14 +220,14 @@ int main(int argc, char **argv) { QJsonObject sessionData; - if(cmd->exists("pick-session")) { + if(cmd_pickSession) { auto *dlg = new SessionDialog(); if(const auto pick = dlg->pickSession()) sessionData = pick.value(); else sessionData = Session::fromCommandLine(profile.value(), urls); - } else if(const auto session = cmd->value("session")) { - QFile sessionJson(session.value()); + } else if(cmd_session) { + QFile sessionJson(QString::fromStdString(args::get(cmd_session))); if(sessionJson.open(QIODevice::ReadOnly | QIODevice::Text)) { sessionData = QJsonDocument::fromJson(sessionJson.readAll()).object(); sessionJson.close(); @@ -176,7 +236,7 @@ int main(int argc, char **argv) sessionData = Session::fromCommandLine(profile.value(), urls); } - if(app.isPrimary() || isStandalone) { + if(app.isPrimary() || cmd_noRemote) { Session::restoreSession(sessionData); } else { // app is secondary and not standalone diff --git a/src/meson.build b/src/meson.build index ea866b9..d2ff9ad 100644 --- a/src/meson.build +++ b/src/meson.build @@ -13,7 +13,7 @@ poi_moc = mod_qt5.preprocess( poi = executable(get_option('poiName'), install: true, cpp_args: ['-DQAPPLICATION_CLASS=QApplication'], - dependencies: [dep_qt5, dep_boost, dep_spdlog, dep_SingleApplication, optional_deps, + dependencies: [dep_qt5, dep_boost, dep_spdlog, dep_SingleApplication, dep_args, optional_deps, dep_about, dep_addressbar, dep_bookmarks, dep_configuration, dep_downloads, dep_pluginloader, dep_urlfilter, dep_webprofile], include_directories: [include], sources: ['main.cpp', 'builtins.cpp', 'crashhandler.cpp', poi_moc, version_h, -- cgit v1.2.1