Browse Source

Use github.com/Taywee/args to parse command line

- This adds 3rd-party/args/args.git subrepository
master
Aqua-sama 1 month ago
parent
commit
c72b2de379
Signed by: Aqua-sama <aqua@iserlohn-fortress.net> GPG Key ID: 81A99984EABC02D1
12 changed files with 107 additions and 203 deletions
  1. +3
    -0
      .gitmodules
  2. +1
    -0
      3rd-party/args/args.git
  3. +5
    -0
      3rd-party/args/meson.build
  4. +0
    -59
      lib/configuration/commandline.cpp
  5. +0
    -79
      lib/configuration/commandline.h
  6. +1
    -1
      lib/configuration/meson.build
  7. +5
    -1
      linux/makepkg/PKGBUILD
  8. +1
    -0
      meson.build
  9. +0
    -27
      src/builtins.cpp
  10. +0
    -5
      src/builtins.h
  11. +90
    -30
      src/main.cpp
  12. +1
    -1
      src/meson.build

+ 3
- 0
.gitmodules View File

@@ -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

+ 1
- 0
3rd-party/args/args.git

@@ -0,0 +1 @@
Subproject commit 78e27faf75ff7d20f232f11ffcef65cde43c449d

+ 5
- 0
3rd-party/args/meson.build View File

@@ -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')
)


+ 0
- 59
lib/configuration/commandline.cpp View File

@@ -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 <QStandardPaths>

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<std::string>()->default_value(defaultUserConfigLocation()), "Set the configuration file.")
("no-remote", "Do not accept or send remote commands.")

("session,s", po::value<std::string>(), "Open the selected session.")
("pick-session", "Show all available sessions and select which one to open.")

("args", po::value<std::vector<std::string>>(), "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());
}
}

+ 0
- 79
lib/configuration/commandline.h View File

@@ -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 <QString>
#include <QStringList>
#include <boost/program_options.hpp>

class CommandLine
{
public:
CommandLine(int argc, char **argv);

bool exists(const char *path) const
{
return (vm.count(path) > 0);
}

template <typename T>
std::optional<T> value(const char *path) const
{
if(vm.count(path) == 0) {
return std::nullopt;
}

if constexpr(std::is_same_v<T, QString>) {
return std::optional<QString>(QString::fromStdString(this->value<std::string>(path).value()));
//return std::optional<QString>(vm[path].as<const char*>());

} else if constexpr(std::is_same_v<T, QStringList>) {
QStringList r;
for(const std::string &item : this->value<std::vector<std::string>>(path).value()) {
r.append(QString::fromStdString(item));
}
return std::optional<QStringList>(r);

} else if constexpr(std::is_same_v<T, std::string>) {

if(vm[path].value().type() == typeid(int)) {
return std::optional<std::string>(std::to_string(vm[path].as<int>()));
}

if(vm[path].value().type() == typeid(bool)) {
return std::optional<std::string>(vm[path].as<bool>() ? "true" : "false");
}

std::string r = vm[path].as<std::string>();

// check if it's a path
if(r.front() == '~') {
r.replace(0, 1, m_homePath);
}

return std::optional<std::string>(r);

} else
return std::optional<T>(vm[path].as<T>());
}

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

+ 1
- 1
lib/configuration/meson.build View File

@@ -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]
)


+ 5
- 1
linux/makepkg/PKGBUILD View File

@@ -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 <aqua@iserlohn-fortress.net>
@@ -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() {

+ 1
- 0
meson.build View File

@@ -81,6 +81,7 @@ subdir('lib/urlfilter')
subdir('lib/webprofile')

subdir('3rd-party/SingleApplication')
subdir('3rd-party/args')

subdir('src')
subdir('lang')

+ 0
- 27
src/builtins.cpp View File

@@ -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;
}

+ 0
- 5
src/builtins.h View File

@@ -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

+ 90
- 30
src/main.cpp View File

@@ -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 <QFile>
#include <QLibraryInfo>
#include <QPluginLoader>
#include <pluginloader.h>
#include <QTranslator>
#include <args.hxx>
#include <iostream>
#include <memory>
#include <plugininterface.h>
#include <pluginloader.h>
#include <spdlog/spdlog.h>

typedef std::function<void(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;

// 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<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;
}

#include <QStandardPaths>
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<CommandLine> cmd = std::make_unique<CommandLine>(argc, argv);
// --version
if(cmd->exists("version")) {
return builtins::version();
}
const std::vector<std::string> 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<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");

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<Configuration> config = std::make_unique<Configuration>(argc, argv, cmd->value<std::string>("config").value());
const std::string config_path = [&]() {
if(cmd_config)
return args::get(cmd_config);
else
return defaultUserConfigLocation();
}();
std::unique_ptr<Configuration> config = std::make_unique<Configuration>(argc, argv, config_path);
QTranslator translator;
if(config->exists("browser.translation")) {
translator.load(config->value<QString>("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<QString>("plugins.path").value(), {"*.so", "*.dll"})) {
for(const QString &path : Util::files(config->value<QString>("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<QString>("profile.default");

app.setConfiguration(config); // app takes ownership of config
app.setup(plugins);

QStringList urls;
if(const auto arguments = cmd->value<std::vector<std::string>>("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<QString>("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

+ 1
- 1
src/meson.build View File

@@ -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,

Loading…
Cancel
Save