diff options
-rw-r--r-- | doc/man/smolbote.1.scd | 3 | ||||
-rw-r--r-- | meson.build | 6 | ||||
-rw-r--r-- | src/bookmarks/builtin.cpp | 108 | ||||
-rw-r--r-- | src/bookmarks/builtins.cpp | 105 | ||||
-rw-r--r-- | src/bookmarks/meson.build | 8 | ||||
-rw-r--r-- | src/browser.cpp | 112 | ||||
-rw-r--r-- | src/browser.h | 20 | ||||
-rw-r--r-- | src/builtins.cpp | 55 | ||||
-rw-r--r-- | src/builtins.h | 28 | ||||
-rw-r--r-- | src/cmd/cmd.hpp | 89 | ||||
-rw-r--r-- | src/cmd/meson.build | 2 | ||||
-rw-r--r-- | src/cmd/test.cpp | 41 | ||||
-rw-r--r-- | src/configuration/builtin.cpp | 41 | ||||
-rw-r--r-- | src/configuration/meson.build | 1 | ||||
-rw-r--r-- | src/main.cpp | 160 | ||||
-rw-r--r-- | src/meson.build | 12 | ||||
-rw-r--r-- | src/session/builtin.cpp | 61 | ||||
-rw-r--r-- | src/session/meson.build | 1 | ||||
-rw-r--r-- | subprojects/args.wrap | 6 | ||||
-rw-r--r-- | test/cli/cli.cpp | 92 | ||||
-rw-r--r-- | test/cli/meson.build | 4 |
21 files changed, 476 insertions, 479 deletions
diff --git a/doc/man/smolbote.1.scd b/doc/man/smolbote.1.scd index 758fda3..2cdc1ba 100644 --- a/doc/man/smolbote.1.scd +++ b/doc/man/smolbote.1.scd @@ -23,6 +23,9 @@ smolbote is a cross-platform keep-it-simple free software web browser that uses - `-s, --session`: Open the selected session. - `--pick-session`: Open all available sessions and select which one to open. +## Env +- 'POI_LOG_PATTERN' + # SEE ALSO *smolboterc*(5) - configuration file and options++ diff --git a/meson.build b/meson.build index 1dd1a1f..272cb60 100644 --- a/meson.build +++ b/meson.build @@ -55,7 +55,7 @@ add_project_arguments(cxx.get_supported_arguments([ '-Wshadow', # if variable declaration shadows one from a parent context # functions '-Wnon-virtual-dtor', # if class with virtual functions has non-virtual dtor - '-Werror=missing-declarations', # missing function declarations in header files + '-Wmissing-declarations', # missing function declarations in header files '-Werror=redundant-decls', '-Woverloaded-virtual', # warn if you overload (not override) a virtual function '-Werror=return-type', @@ -95,7 +95,6 @@ dep_spdlog = dependency('spdlog', fallback: ['spdlog', 'spdlog_dep'], version: ' dep_gtest = dependency('gtest', required: false, disabler: true) dep_catch = dependency('catch2', required: true, fallback: ['catch2', 'catch2_dep'] ) dep_SingleApplication = dependency('singleapplication', fallback: [ 'singleapplication', 'SingleApplication_dep' ]) -dep_args = dependency('args.hxx', fallback: [ 'args', 'args_dep' ]) # Generate config header poi_sourceset = sourceset.source_set() @@ -113,7 +112,6 @@ subdir('lib/smolblok') subdir('src') subdir('lang') subdir('doc') -subdir('tools') subdir('plugins/ProfileEditor') subdir('plugins/HostlistFilter') @@ -127,7 +125,7 @@ poi_exe = executable(get_option('poi'), cpp_args: ['-DQAPPLICATION_CLASS=QApplication'], sources: [ssconfig.sources()], include_directories: [ plugininterfaces_include, include_directories('src') ], - dependencies: [ dep_qt5, dep_spdlog, dep_SingleApplication, dep_args, dep_bookmarks, dep_configuration, dep_downloads, dep_pluginloader, ssconfig.dependencies(), lib_session_formats ], + dependencies: [ dep_qt5, dep_spdlog, dep_SingleApplication, dep_bookmarks, dep_configuration, dep_downloads, dep_pluginloader, ssconfig.dependencies(), lib_session_formats ], install: true, ) diff --git a/src/bookmarks/builtin.cpp b/src/bookmarks/builtin.cpp new file mode 100644 index 0000000..732aa64 --- /dev/null +++ b/src/bookmarks/builtin.cpp @@ -0,0 +1,108 @@ +/* + * 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/cgit/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "bookmarkmodel.h" +#include "browser.h" +#include "configuration.h" +#include <QBuffer> +#include <QCommandLineParser> +#include <QCoreApplication> +#include <QFile> +#include <cstdlib> +#include <iostream> +#include <spdlog/spdlog.h> + +template <auto T> +[[nodiscard]] inline bool import(BookmarkModel *model, const QString &path) +{ + QFile f(path); + if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { + BookmarkFormat<T>(&f) >> model; + f.close(); + return true; + } + return false; +} + +namespace builtins +{ +int sub_bookmarks(const QStringList &l, Browser &) +{ + const QCommandLineOption output({ "o", "output" }, "Output location (default: append to browser bookmarks).", "file"); + + const QCommandLineOption import_xbel({ "x", "import-xbel" }, "Import xbel format.", "xbel"); + const QCommandLineOption import_json({ "j", "import-json" }, "Import json format.", "json"); + + QCommandLineParser parser; + parser.setApplicationDescription("smolbote: bookmarks"); + parser.addHelpOption(); + + parser.addOptions({ output, import_xbel, import_json }); + + if(l.count() <= 1) { + parser.showHelp(); + } + + parser.process(l); + Configuration conf; + const auto bookmarks_path = conf.value<QString>("bookmarks.path").value(); + auto *model = new BookmarkModel; + + // implicit bookmarks.path import + if(!parser.isSet(output)) { + if(!import<XbelFormat>(model, bookmarks_path)) { + spdlog::error("Could not open %s", qUtf8Printable(bookmarks_path)); + } + } + + for(const auto &i : parser.values(import_xbel)) { + if(!import<XbelFormat>(model, i)) { + spdlog::error("Could not open %s", qUtf8Printable(i)); + } + } + + for(const auto &i : parser.values(import_json)) { + if(!import<FirefoxJsonFormat>(model, i)) { + spdlog::error("Could not open %s", qUtf8Printable(i)); + } + } + + QIODevice *out = nullptr; + if(!parser.isSet(output)) { + out = new QFile(bookmarks_path); + } else { + const auto o = parser.value(output); + + if(o == "stdout") { + out = new QBuffer; + QObject::connect(out, &QIODevice::aboutToClose, [out]() { + out->seek(0); + std::cout << qUtf8Printable(out->readAll()) << std::endl; + }); + + } else { + out = new QFile(o); + } + } + + if(!out->isOpen()) { + if(!out->open(QIODevice::ReadWrite | QIODevice::Text)) { + spdlog::error("Could not open output"); + return EXIT_FAILURE; + } + } + + BookmarkFormat<XbelFormat> format(out); + format << model; + out->close(); + + delete out; + delete model; + return EXIT_FAILURE; +} +} // namespace diff --git a/src/bookmarks/builtins.cpp b/src/bookmarks/builtins.cpp deleted file mode 100644 index f7b6f3f..0000000 --- a/src/bookmarks/builtins.cpp +++ /dev/null @@ -1,105 +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 "builtins.h" -#include "bookmarkmodel.h" -#include "configuration.h" -#include <QBuffer> -#include <QFile> -#include <iostream> -#include <spdlog/spdlog.h> - -int builtins::bookmarks(const std::string &progname, std::vector<std::string>::const_iterator beginargs, std::vector<std::string>::const_iterator endargs) -{ - args::ArgumentParser parser("bookmarks", - "If an output location is not specified, this command will append imported bookmarks to the browser's bookmarks.\n" - "If an output location is specified, this command will load all XBEL-format bookmarks, then all JSON-format bookmarks, and write them to the output location."); - parser.Prog(progname); - - args::HelpFlag help(parser, "help", "Display this help message and exit.", { 'h', "help" }); - args::ValueFlag<std::string> save(parser, "file, stdout", "Output location.", { 'e', "export" }); - args::ValueFlagList<std::string> import_xbel(parser, "bookmarks.xbel", "Import xbel format.", { 'x', "import-xbel" }); - args::ValueFlagList<std::string> import_json(parser, "bookmarks.json", "Import json format.", { 'j', "import-json" }); - - try { - parser.ParseArgs(beginargs, endargs); - } catch(args::Help &) { - std::cout << parser; - return 0; - } catch(args::Error &e) { - std::cerr << e.what() << std::endl; - std::cerr << parser; - return -1; - } - - auto *model = new BookmarkModel; - - // implicit bookmarks.path import - if(!save) { - Configuration conf; - QFile f(conf.value<QString>("bookmarks.path").value()); - if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { - BookmarkFormat<XbelFormat>(&f) >> model; - f.close(); - } - } - - for(const auto &i : args::get(import_xbel)) { - QFile f(QString::fromStdString(i)); - if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { - BookmarkFormat<XbelFormat>(&f) >> model; - f.close(); - } else { - spdlog::error("Could not open %s", i); - return -1; - } - } - - for(const auto &i : args::get(import_json)) { - QFile f(QString::fromStdString(i)); - if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { - BookmarkFormat<FirefoxJsonFormat>(&f) >> model; - f.close(); - } else { - spdlog::error("Could not open %s", i); - return -1; - } - } - - QIODevice *output = nullptr; - if(save && args::get(save) == "stdout") { - output = new QBuffer; - QObject::connect(output, &QIODevice::aboutToClose, [output]() { - output->seek(0); - std::cout << qUtf8Printable(output->readAll()) << std::endl; - }); - - output->open(QIODevice::ReadWrite | QIODevice::Text); - - } else if(!save) { - Configuration conf; - output = new QFile(conf.value<QString>("bookmarks.path").value()); - } else { - output = new QFile(QString::fromStdString(args::get(save))); - } - - if(!output->isOpen()) { - if(!output->open(QIODevice::ReadWrite | QIODevice::Text)) { - spdlog::error("Could not open output"); - return -1; - } - } - - BookmarkFormat<XbelFormat> format(output); - format << model; - output->close(); - - delete output; - - return 0; -} diff --git a/src/bookmarks/meson.build b/src/bookmarks/meson.build new file mode 100644 index 0000000..8fc93f0 --- /dev/null +++ b/src/bookmarks/meson.build @@ -0,0 +1,8 @@ +poi_sourceset.add(files('builtin.cpp', + 'bookmarkswidget.cpp', 'editbookmarkdialog.cpp', 'bookmarkstoolbar.cpp'), +mod_qt5.preprocess( + moc_headers: ['bookmarkswidget.h', 'editbookmarkdialog.h'], + ui_files: ['bookmarksform.ui', 'editbookmarkdialog.ui'], + dependencies: dep_qt5 +)) + diff --git a/src/browser.cpp b/src/browser.cpp index aec3ad1..894216e 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -1,7 +1,7 @@ /* * 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 + * location: https://neueland.iserlohn-fortress.net/cgit/smolbote * * SPDX-License-Identifier: GPL-3.0 */ @@ -16,6 +16,8 @@ #include "mainwindow/addressbar.h" #include "mainwindow/mainwindow.h" #include "mainwindow/menubar.h" +#include "session_json.hpp" +#include "settings.h" #include "smolbote/plugininterface.hpp" #include "subwindow/subwindow.h" #include "util.h" @@ -35,13 +37,46 @@ Browser::Browser(int &argc, char *argv[], bool allowSecondary) : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion) { - Configuration conf; - - setApplicationName(conf.value<QString>("poi.name").value()); + //setApplicationName(conf.value<QString>("poi.name").value()); + setApplicationName("smolbote"); setWindowIcon(Util::icon<Util::AppWindowIcon>()); setApplicationVersion(QVersionNumber::fromString(QLatin1String(poi_Version)).toString()); +} + +Browser::~Browser() +{ + if(m_bookmarks) + m_bookmarks->save(); + + for(auto *info : qAsConst(m_plugins)) + delete info; + + qDeleteAll(m_windows); + m_windows.clear(); +} + +void Browser::about() +{ + auto *dlg = new AboutDialog(activeWindow()); + dlg->show(); +} - if(const auto _translation = conf.value<QString>("browser.translation")) { +void Browser::aboutPlugins() +{ + auto *dlg = new AboutPluginDialog; + for(auto *info : qAsConst(m_plugins)) { + dlg->add(info->loader); + } + dlg->exec(); +} + +void Browser::loadConfiguration(const QString &path) +{ + auto ctx = init_conf(path.toStdString()); + spdlog::info("Using configuration [{}]: {}", ctx.path, ctx.ptr->make_global() ? "okay" : "failed"); + m_conf = std::move(ctx.ptr); + + if(const auto _translation = m_conf->value<QString>("browser.translation")) { auto *translator = new QTranslator(this); if(translator->load(_translation.value())) installTranslator(translator); @@ -49,7 +84,7 @@ Browser::Browser(int &argc, char *argv[], bool allowSecondary) delete translator; } - if(const auto _locale = conf.value<QString>("browser.locale")) { + if(const auto _locale = m_conf->value<QString>("browser.locale")) { auto *locale = new QTranslator(this); if(locale->load("qt_" + _locale.value(), QLibraryInfo::location(QLibraryInfo::TranslationsPath))) installTranslator(locale); @@ -57,11 +92,11 @@ Browser::Browser(int &argc, char *argv[], bool allowSecondary) delete locale; } - if(auto iconTheme = conf.value<QString>("browser.iconTheme")) { + if(auto iconTheme = m_conf->value<QString>("browser.iconTheme")) { QIcon::setThemeName(iconTheme.value()); } - if(auto stylesheet = conf.value<QString>("browser.stylesheet")) { + if(auto stylesheet = m_conf->value<QString>("browser.stylesheet")) { QFile f(stylesheet.value()); if(f.open(QIODevice::ReadOnly)) { setStyleSheet(f.readAll()); @@ -70,20 +105,20 @@ Browser::Browser(int &argc, char *argv[], bool allowSecondary) } // content filter - register format plugins - if(const auto hostlist_plugin = conf.value<QString>("smolblok.plugins.hostlist")) { + if(const auto hostlist_plugin = m_conf->value<QString>("smolblok.plugins.hostlist")) { content_filter.registerFormatPlugin("hostlist", hostlist_plugin.value()); } - if(const auto adblock_plugin = conf.value<QString>("smolblok.plugins.adblock")) { + if(const auto adblock_plugin = m_conf->value<QString>("smolblok.plugins.adblock")) { content_filter.registerFormatPlugin("adblock", adblock_plugin.value()); } // load profiles { - const auto profiles = Util::files(conf.value<QString>("profile.path").value(), { "*.profile" }); - const auto search = conf.value<QString>("profile.search").value(); - const auto homepage = QUrl::fromUserInput(conf.value<QString>("profile.homepage").value()); - const auto newtab = QUrl::fromUserInput(conf.value<QString>("profile.newtab").value()); - const auto default_id = conf.value<QString>("profile.default").value(); + const auto profiles = Util::files(m_conf->value<QString>("profile.path").value(), { "*.profile" }); + const auto search = m_conf->value<QString>("profile.search").value(); + const auto homepage = QUrl::fromUserInput(m_conf->value<QString>("profile.homepage").value()); + const auto newtab = QUrl::fromUserInput(m_conf->value<QString>("profile.newtab").value()); + const auto default_id = m_conf->value<QString>("profile.default").value(); m_profileManager = std::make_unique<WebProfileManager<false>>(profiles, default_id, search, homepage, newtab); m_profileManager->make_global(); @@ -97,14 +132,14 @@ Browser::Browser(int &argc, char *argv[], bool allowSecondary) } // downloads - m_downloads = std::make_unique<DownloadsWidget>(conf.value<QString>("downloads.path").value()); + m_downloads = std::make_unique<DownloadsWidget>(m_conf->value<QString>("downloads.path").value()); m_profileManager->walk([this](const QString &, WebProfile *profile, QSettings *) { profile->setUrlRequestInterceptor(content_filter.interceptor()); connect(profile, &QWebEngineProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload); }); // bookmarks - m_bookmarks = std::make_shared<BookmarksWidget>(QString::fromStdString(conf.value<std::string>("bookmarks.path").value())); + m_bookmarks = std::make_shared<BookmarksWidget>(m_conf->value<QString>("bookmarks.path").value()); connect(m_bookmarks.get(), &BookmarksWidget::openUrl, this, [this](const QUrl &url) { m_windows.last()->createTab(url); }); @@ -114,33 +149,6 @@ Browser::Browser(int &argc, char *argv[], bool allowSecondary) timer->start(5 * 60 * 1000); // 5min * 60sec * 1000ms } -Browser::~Browser() -{ - if(m_bookmarks) - m_bookmarks->save(); - - for(auto *info : qAsConst(m_plugins)) - delete info; - - qDeleteAll(m_windows); - m_windows.clear(); -} - -void Browser::about() -{ - auto *dlg = new AboutDialog(activeWindow()); - dlg->show(); -} - -void Browser::aboutPlugins() -{ - auto *dlg = new AboutPluginDialog; - for(auto *info : qAsConst(m_plugins)) { - dlg->add(info->loader); - } - dlg->exec(); -} - bool Browser::loadPlugin(const QString &path) { if(path.isEmpty()) { @@ -167,6 +175,22 @@ bool Browser::loadPlugin(const QString &path) return true; } +void Browser::enableRemote(bool toggle) +{ + if(static_cast<bool>(remoteConnection) == toggle) + return; + + if(!toggle) { + disconnect(remoteConnection); + return; + } + + remoteConnection = connect(this, &Browser::receivedMessage, this, [this](quint32, const QByteArray &message) { + JsonSession s(message); + this->open(s.get()); + }); +} + void Browser::showWidget(QWidget *widget, MainWindow *where) const { bool wasVisible = widget->isVisible(); diff --git a/src/browser.h b/src/browser.h index 69cf804..8e443fe 100644 --- a/src/browser.h +++ b/src/browser.h @@ -1,7 +1,7 @@ /* * 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 + * location: https://neueland.iserlohn-fortress.net/cgit/smolbote * * SPDX-License-Identifier: GPL-3.0 */ @@ -37,26 +37,32 @@ public: return qAsConst(m_windows); } - BookmarksWidget *bookmarks() const + [[deprecated]] BookmarksWidget *bookmarks() const { return m_bookmarks.get(); } - DownloadsWidget *downloads() const + [[deprecated]] DownloadsWidget *downloads() const { return m_downloads.get(); } + bool remoteEnabled() const + { + return static_cast<bool>(remoteConnection); + } signals: void pluginAdded(QPluginLoader *); public slots: - void about(); - void aboutPlugins(); + [[deprecated]] void about(); + [[deprecated]] void aboutPlugins(); - void showWidget(QWidget *widget, MainWindow *where) const; + [[deprecated]] void showWidget(QWidget *widget, MainWindow *where) const; void open(const QVector<Session::MainWindow> &data, bool merge = true); + void loadConfiguration(const QString &path); bool loadPlugin(const QString &path); + void enableRemote(bool toggle); private: struct PluginInfo { @@ -77,6 +83,8 @@ private: Q_DISABLE_COPY(Browser) + QMetaObject::Connection remoteConnection; + std::unique_ptr<Configuration> m_conf; std::shared_ptr<BookmarksWidget> m_bookmarks; std::unique_ptr<DownloadsWidget> m_downloads; std::unique_ptr<WebProfileManager<false>> m_profileManager{ nullptr }; diff --git a/src/builtins.cpp b/src/builtins.cpp deleted file mode 100644 index ab5942c..0000000 --- a/src/builtins.cpp +++ /dev/null @@ -1,55 +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 "builtins.h" -#include "configuration.h" -#include "version.h" -#include <QObject> -#include <QVersionNumber> -#include <iostream> - -int builtins::version() -{ - const auto version = QVersionNumber::fromString(QLatin1String(poi_Version)).toString().toStdString(); - std::cout << "smolbote " << version << std::endl; - return 0; -} - -int builtins::build() -{ - std::cout << poi_Version << std::endl; - return 0; -} - -int builtins::configuration(const std::string &progname, std::vector<std::string>::const_iterator beginargs, std::vector<std::string>::const_iterator endargs) -{ - args::ArgumentParser parser("configuration"); - parser.Prog(progname); - - args::HelpFlag help(parser, "help", "Display this help message and exit.", { 'h', "help" }); - args::Flag dump(parser, "dump", "Dump currently used configuration and exit", { "dump" }); - - try { - parser.ParseArgs(beginargs, endargs); - } catch(args::Help &e) { - std::cout << parser; - return 0; - } catch(args::Error &e) { - std::cerr << e.what() << std::endl; - std::cerr << parser; - return -1; - } - - if(dump) { - Configuration conf; - std::cout << conf << std::endl; - return 0; - } - - return 0; -} diff --git a/src/builtins.h b/src/builtins.h deleted file mode 100644 index 9fdca98..0000000 --- a/src/builtins.h +++ /dev/null @@ -1,28 +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_BUILTINS_H -#define SMOLBOTE_BUILTINS_H - -#include "smolbote/plugininterface.hpp" -#include <args.hxx> - -typedef std::function<int(const std::string &, std::vector<std::string>::const_iterator, std::vector<std::string>::const_iterator)> subcommand_func; -typedef std::unordered_map<std::string, subcommand_func> command_map; - -namespace builtins -{ -int version(); -int build(); - -int configuration(const std::string &progname, std::vector<std::string>::const_iterator beginargs, std::vector<std::string>::const_iterator endargs); - -int bookmarks(const std::string &progname, std::vector<std::string>::const_iterator beginargs, std::vector<std::string>::const_iterator endargs); -} - -#endif diff --git a/src/cmd/cmd.hpp b/src/cmd/cmd.hpp new file mode 100644 index 0000000..37bb3ce --- /dev/null +++ b/src/cmd/cmd.hpp @@ -0,0 +1,89 @@ +/* + * 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/cgit/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_CMD_HPP +#define SMOLBOTE_CMD_HPP + +#include "version.h" +#include <QCommandLineParser> +#include <QCoreApplication> +#include <functional> +#include <iostream> + +namespace command_line +{ +template <typename T> +using subcommand_fn = std::function<int(const QStringList &, T &)>; +template <typename T> +using map = std::unordered_map<std::string, subcommand_fn<T>>; + +// a helper function to join the keys of a command_map into a string +template <typename T> +[[nodiscard]] inline QString join_keys(const map<T> &map, const QString sep = ", ") +{ + QString k; + for(auto it = map.cbegin(); it != map.cend(); ++it) { + k += QString::fromStdString(it->first); + if(std::next(it) != map.cend()) { + k += sep; + } + } + return k; +} + +template <typename T> +[[nodiscard]] std::function<int(T &)> process(T &app, const map<T> &map, const std::string &d) +{ + const QCommandLineOption build({ "b", "build" }, "Display build information."); + const QCommandLineOption config({ "c", "config" }, "Set the configuration file.", "file"); + const QCommandLineOption noRemote("no-remote", "Do not accept or send remote commands."); + + QCommandLineParser parser; + parser.setApplicationDescription("smolbote: yet another no-frills browser"); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addOption(build); + parser.addOption(config); + parser.addOption(noRemote); + + parser.addPositionalArgument("subcommand", QString("%1; default: %2").arg(join_keys(map), QString::fromStdString(d))); + parser.addPositionalArgument("urls", "List of URLs to open."); + + parser.process(app); + + if(parser.isSet(build)) { + std::cout << app.applicationName().toStdString() << " " << poi_Version << std::endl; + exit(0); + } + + if constexpr(requires { app.loadConfiguration(parser.value(config)); }) { + app.loadConfiguration(parser.value(config)); + } else { + qDebug("warning: cannot init configuration"); + } + + if constexpr(requires { app.enableRemote(true); }) { + app.enableRemote(!parser.isSet(noRemote)); + } + + const auto args = parser.positionalArguments(); + + if(args.count() >= 1) { + auto i = map.find(args.first().toStdString()); + if(i != map.end()) + return std::bind(i->second, args, std::placeholders::_1); + } + + return std::bind(map.at(d), args, std::placeholders::_1); +} + +} // namespace + +#endif diff --git a/src/cmd/meson.build b/src/cmd/meson.build new file mode 100644 index 0000000..466647f --- /dev/null +++ b/src/cmd/meson.build @@ -0,0 +1,2 @@ +test('command line parser', executable('cmd_test', ['test.cpp', version_h], dependencies: [ dep_qt5 ])) + diff --git a/src/cmd/test.cpp b/src/cmd/test.cpp new file mode 100644 index 0000000..fc102ff --- /dev/null +++ b/src/cmd/test.cpp @@ -0,0 +1,41 @@ +#include "cmd.hpp" +#include <QCoreApplication> + +int sub_bookmarks(const QStringList &l, QCoreApplication &) +{ + const QCommandLineOption q("q", "something"); + QCommandLineParser parser; + parser.setApplicationDescription("testing bookmarks editor"); + parser.addHelpOption(); + parser.addOption(q); + + if(l.count() <= 1) { + parser.showHelp(); + } + parser.process(l); + return 0; +} + +int sub_list(const QStringList &args, QCoreApplication &) +{ + for(const auto &x : args) { + qDebug("-%s", qUtf8Printable(x)); + } + return 0; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + app.setApplicationName("cmd_test"); + app.setApplicationVersion("0.1.0"); + + const command_line::map<QCoreApplication> m{ + { "bookmarks", &sub_bookmarks }, + { "list", &sub_list } + }; + + const auto f = command_line::process<QCoreApplication>(app, m, "list"); + return f(app); +} + diff --git a/src/configuration/builtin.cpp b/src/configuration/builtin.cpp new file mode 100644 index 0000000..a6ed002 --- /dev/null +++ b/src/configuration/builtin.cpp @@ -0,0 +1,41 @@ +/* + * 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/cgit/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "browser.h" +#include "configuration.h" +#include <QCommandLineParser> +#include <QCoreApplication> +#include <cstdlib> +#include <iostream> + +namespace builtins +{ +int sub_configuration(const QStringList &l, Browser &) +{ + const QCommandLineOption dump({ "d", "dump" }, "Print the currently used configuration and exit."); + + QCommandLineParser parser; + parser.setApplicationDescription("configuration"); + parser.addHelpOption(); + parser.addOption(dump); + + if(l.count() <= 1) { + parser.showHelp(); + } + + parser.process(l); + + if(parser.isSet(dump)) { + Configuration conf; + std::cout << conf << std::endl; + return EXIT_SUCCESS; + } + + return EXIT_FAILURE; +} +} diff --git a/src/configuration/meson.build b/src/configuration/meson.build new file mode 100644 index 0000000..5ba8ffd --- /dev/null +++ b/src/configuration/meson.build @@ -0,0 +1 @@ +poi_sourceset.add(files('builtin.cpp')) diff --git a/src/main.cpp b/src/main.cpp index 97b529b..88fa657 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,162 +1,66 @@ /* * 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 + * location: https://neueland.iserlohn-fortress.net/cgit/smolbote * * SPDX-License-Identifier: GPL-3.0 */ #include "browser.h" -#include "builtins.h" +#include "cmd/cmd.hpp" #include "configuration.h" -#include "session/sessiondialog.h" -#include "session_json.hpp" -#include "settings.h" #include "util.h" -#include "version.h" #include <QFile> #include <QPluginLoader> #include <QStandardPaths> -#include <iostream> -#include <memory> #include <pluginloader.h> #include <spdlog/spdlog.h> -// 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 = ", ") +namespace builtins { - std::vector<std::string> 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; +int sub_configuration(const QStringList &l, Browser &); +int sub_bookmarks(const QStringList &l, Browser &); +int sub_session(const QStringList &l, Browser &); } int main(int argc, char **argv) { - // change log pattern - spdlog::set_pattern("[%^%l%$] [%P:%t] %v"); + // spdlog pattern + if(const char *env_spdlog = std::getenv("POI_LOG_PATTERN")) + spdlog::set_pattern(env_spdlog); + else + spdlog::set_pattern("[%^%l%$] [%P:%t] %v"); + #ifdef QT_DEBUG spdlog::set_level(spdlog::level::debug); // Set global log level to debug #endif - const command_map commands{ - { "configuration", builtins::configuration }, - { "bookmarks", builtins::bookmarks }, + const command_line::map<Browser> subcommands{ + { "configuration", builtins::sub_configuration }, + { "bookmarks", builtins::sub_bookmarks }, + { "session", builtins::sub_session }, }; - const std::vector<std::string> args(argv + 1, argv + argc); - args::ArgumentParser parser("smolbote: yet another no-frills browser", "Subcommands: " + join_keys(commands)); - 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<std::string> 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<std::string> cmd_session(parser, "session", "Open the specified session.", { 's', "session" }); - - args::PositionalList<std::string> cmd_args(parser, "URL(s)", "List of URLs to open"); - cmd_args.KickOut(true); - - try { - auto next = parser.ParseArgs(args); - - if(cmd_version) - return builtins::version(); - if(cmd_build) - return builtins::build(); - - // create and load configuration - auto conf = init_conf(args::get(cmd_config)); - conf.ptr->make_global(); - spdlog::debug("Loading configuration {}", conf.path); + // argc, argv, allowSecondary + Browser app(argc, argv); + // set this, otherwise the webview becomes black when using a stylesheet + app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); - if(cmd_args) { - const auto front = args::get(cmd_args).front(); - const auto cmd = commands.find(front); - if(cmd != commands.end()) { - return cmd->second(argv[0], next, std::end(args)); - } - } - - // argc, argv, allowSecondary - Browser app(argc, argv); - // set this, otherwise the webview becomes black when using a stylesheet - app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); - - { - // load plugins - for(const QString &path : Util::files(conf.ptr->value<QString>("plugins.path").value(), { "*.so", "*.dll" })) { - if(app.loadPlugin(path)) { - spdlog::debug("Loaded plugin [{}]", qUtf8Printable(path)); - } else { - spdlog::warn("Failed loading plugin [{}]", qUtf8Printable(path)); - } - } - } - - const auto profile = conf.ptr->value<QString>("profile.default").value(); - - QStringList urls; - for(const auto &u : args::get(cmd_args)) { - urls.append(QString::fromStdString(u)); - } - if(urls.isEmpty()) { - urls.append(QString()); - } + const auto f = command_line::process<Browser>(app, subcommands, "session"); - // if app is primary, create new sessions from received messages - if(app.isPrimary() && !cmd_noRemote) { - QObject::connect(&app, &Browser::receivedMessage, &app, [&app](quint32 instanceId, QByteArray message) { - Q_UNUSED(instanceId); - JsonSession session(message); - app.open(session.get()); - }); - } - - { - const auto session = [&]() { - if(cmd_session) { - QFile sessionJson(QString::fromStdString(args::get(cmd_session))); - if(sessionJson.open(QIODevice::ReadOnly | QIODevice::Text)) { - return JsonSession(sessionJson.readAll()); - } - } - if(cmd_pickSession) { - SessionDialog dlg; - if(const auto pick = dlg.pickSession()) { - return JsonSession(pick.value()); - } - } - return JsonSession(profile, urls); - }(); - - if(app.isPrimary() || cmd_noRemote) { - app.open(session.get()); + { + Configuration conf; + const auto plugins_path = conf.value<QString>("plugins.path").value(); + // load plugins + /*for(const QString &path : Util::files(conf.value<QString>("plugins.path").value(), { "*.so", "*.dll" })) { + if(app.loadPlugin(path)) { + spdlog::debug("Loaded plugin [{}]", qUtf8Printable(path)); } else { - // app is secondary and not standalone - return app.sendMessage(session.serialize()); + spdlog::warn("Failed loading plugin [{}]", qUtf8Printable(path)); } - } - - return app.exec(); + }*/ + } - } catch(args::Help &) { - std::cout << parser; - return 0; + return f(app); - } catch(args::Error &e) { - std::cerr << e.what() << std::endl; - std::cerr << parser; - return -1; - } } diff --git a/src/meson.build b/src/meson.build index f3ef381..1529a95 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,25 +20,25 @@ poi_icons_h = custom_target('poi_icons_h', command: [ python3, rcc_exe, '-o=@OUTPUT@', 'dump', '-ns=icons', '@INPUT@' ]) subdir('about') +subdir('bookmarks') +subdir('configuration') +subdir('cmd') +subdir('session') subdir('webengine') poi_sourceset.add(mod_qt5.preprocess( moc_headers: ['browser.h', 'applicationmenu.h', 'mainwindow/mainwindow.h', 'mainwindow/addressbar.h', 'mainwindow/menubar.h', 'mainwindow/widgets/completer.h', 'mainwindow/widgets/urllineedit.h', 'mainwindow/widgets/dockwidget.h', 'mainwindow/widgets/navigationbar.h', 'mainwindow/widgets/searchform.h', - 'bookmarks/bookmarkswidget.h', 'bookmarks/editbookmarkdialog.h', 'session/savesessiondialog.h', 'session/sessiondialog.h', 'subwindow/subwindow.h', 'subwindow/tabwidget.h' ], ui_files: [ 'mainwindow/addressbar.ui', 'mainwindow/widgets/searchform.ui', - 'bookmarks/bookmarksform.ui', 'bookmarks/editbookmarkdialog.ui', 'session/savesessiondialog.ui', 'session/sessiondialog.ui' ], dependencies: dep_qt5 )) poi_sourceset.add(files( - 'main.cpp', 'builtins.cpp', - 'browser.cpp', 'applicationmenu.cpp', - 'util.cpp', 'util.h', + 'main.cpp', 'browser.cpp', 'applicationmenu.cpp', 'util.cpp', 'mainwindow/mainwindow.cpp', 'mainwindow/addressbar.cpp', @@ -50,8 +50,6 @@ poi_sourceset.add(files( 'mainwindow/widgets/navigationbar.cpp', 'mainwindow/widgets/searchform.cpp', - 'bookmarks/builtins.cpp', 'bookmarks/bookmarkswidget.cpp', 'bookmarks/editbookmarkdialog.cpp', 'bookmarks/bookmarkstoolbar.cpp', - 'session/savesessiondialog.cpp', 'session/sessiondialog.cpp', diff --git a/src/session/builtin.cpp b/src/session/builtin.cpp new file mode 100644 index 0000000..0359b42 --- /dev/null +++ b/src/session/builtin.cpp @@ -0,0 +1,61 @@ +/* + * 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/cgit/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "browser.h" +#include "configuration.h" +#include "session/sessiondialog.h" +#include "session_json.hpp" +#include <QCommandLineParser> +#include <variant> + +namespace builtins +{ +int sub_session(const QStringList &args, Browser &app) +{ + const auto default_profile = []() { + Configuration conf; + return conf.value<QString>("profile.default").value(); + }(); + + const auto session = args.count() == 0 ? JsonSession(default_profile, args) : [&]() { + const QCommandLineOption profile({ "p", "profile" }, "Create session with specified profile.", "profile-id", default_profile); + const QCommandLineOption pick("pick", "Show all available sessions and select which one to open."); + const QCommandLineOption open({ "o", "open" }, "Open the specified session.", "file"); + + QCommandLineParser parser; + parser.setApplicationDescription("session"); + parser.addHelpOption(); + parser.addOptions({ pick, open, profile }); + + parser.process(args); + + if(parser.isSet(pick)) { + SessionDialog dlg; + if(const auto s = dlg.pickSession()) { + return JsonSession(s.value()); + } + + } else if(parser.isSet(open)) { + QFile sessionJson(parser.value(open)); + if(sessionJson.open(QIODevice::ReadOnly | QIODevice::Text)) { + return JsonSession(sessionJson.readAll()); + } + } + + return JsonSession(parser.isSet(profile) ? parser.value(profile) : default_profile, args); + }(); + + if(app.isPrimary() || !app.remoteEnabled()) { + app.open(session.get()); + return app.exec(); + } + + app.sendMessage(session.serialize()); + return 0; +} +} diff --git a/src/session/meson.build b/src/session/meson.build new file mode 100644 index 0000000..5ba8ffd --- /dev/null +++ b/src/session/meson.build @@ -0,0 +1 @@ +poi_sourceset.add(files('builtin.cpp')) diff --git a/subprojects/args.wrap b/subprojects/args.wrap deleted file mode 100644 index ac19afa..0000000 --- a/subprojects/args.wrap +++ /dev/null @@ -1,6 +0,0 @@ -[wrap-file] -directory = args.hxx-6.2.2 - -source_url = https://neueland.iserlohn-fortress.net/releases/args.hxx-6.2.2.tar.xz -source_filename = args.hxx-6.2.2.tar.xz -source_hash = c1ed4bc76d3c343f493e6ae2c10ebcf3fdfaf013210b0a3dead04cef30c63fb6 diff --git a/test/cli/cli.cpp b/test/cli/cli.cpp deleted file mode 100644 index 322c365..0000000 --- a/test/cli/cli.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <linenoise.h> -#include <Python.h> - -static int numargs=0; - -/* Return the number of arguments of the application command line */ -static PyObject* emb_numargs(PyObject *self, PyObject *args) -{ - if(!PyArg_ParseTuple(args, ":numargs")) - return NULL; - - numargs = 19; - return PyLong_FromLong(numargs); -} - -static PyMethodDef EmbMethods[] = { - /* ml_name Name of the method - * ml_meth pointer to C implementation - * ml_flags flag bits indicating how it should be called - * ml_doc docstring - */ - {"numargs", emb_numargs, METH_VARARGS, "Return the number of arguments received by the process."}, - {NULL, NULL, 0, NULL} -}; - -static PyModuleDef EmbModule = { - /* m_base */ PyModuleDef_HEAD_INIT, // base module, always HEAD_INIT - /* m_name */ "emb", // module name - /* m_doc */ NULL, // Docstring for the module; usually a docstring variable created with PyDoc_STRVAR() - /* m_size */ -1, // - /* m_methods */ EmbMethods, // A pointer to a table of module-level functions - /* m_slots */ NULL, // An array of slot definitions for multi-phase initialization - /* traverse */ NULL, // A traversal function to call during GC traversal of the module object - /* clear */ NULL, // A clear function to call during GC clearing of the module object - /* free */ NULL // A function to call during deallocation of the module object -}; - -static PyObject* PyInit_emb(void) -{ - return PyModule_Create(&EmbModule); -} - -int main(int argc, char** argv) -{ - printf("cli test application\n"); - - wchar_t *program = Py_DecodeLocale(argv[0], NULL); - if (program == NULL) { - fprintf(stderr, "Fatal error: cannot decode argv[0]\n"); - exit(1); - } - - // inform the interpreter about paths to run-time libraries - Py_SetProgramName(program); /* optional but recommended */ - - printf("import emb: %i\n", PyImport_AppendInittab("emb", &PyInit_emb)); - - // Initialize the python interpreter - Py_Initialize(); - - PyRun_SimpleString("print('Python interpreter ready')\n"); - - const char* prompt = "poi> "; - - while(true) { - char *cmd = linenoise(prompt); - - if(cmd == nullptr || *cmd == '\0') { - printf("breaking out of repl\n"); - free(cmd); - break; - } - - //printf("echo(%i):'%s'\n", strlen(cmd), cmd); - PyRun_SimpleString(cmd); - linenoiseHistoryAdd(cmd); - free(cmd); - } - - // finalize the interpreter - if (Py_FinalizeEx() < 0) { - exit(120); - } - - // - PyMem_RawFree(program); - - return 0; -} - diff --git a/test/cli/meson.build b/test/cli/meson.build deleted file mode 100644 index 298e1db..0000000 --- a/test/cli/meson.build +++ /dev/null @@ -1,4 +0,0 @@ -cli_demo = executable('cli', install: false, dependencies: [ optional_deps ], - sources: [ 'cli.cpp' ] -) - |