fix(BRIDGE-8): more robust command-line args parser in bridge-gui.

fix(BRIDGE-8): add command-line invocation to log.
This commit is contained in:
Xavier Michelon 2024-04-10 14:30:18 +02:00
parent bb15efa711
commit c692c21b87
7 changed files with 194 additions and 135 deletions

View File

@ -21,9 +21,7 @@ import (
"testing"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/bradenaw/juniper/xslices"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/slices"
)
func TestFindAndStrip(t *testing.T) {
@ -31,53 +29,53 @@ func TestFindAndStrip(t *testing.T) {
result, found := findAndStrip(list, "a")
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"b", "c", "c", "b", "c"}))
assert.Equal(t, result, []string{"b", "c", "c", "b", "c"})
result, found = findAndStrip(list, "c")
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a", "b", "b"}))
assert.Equal(t, result, []string{"a", "b", "b"})
result, found = findAndStrip([]string{"c", "c", "c"}, "c")
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{}))
assert.Equal(t, result, []string{})
result, found = findAndStrip(list, "A")
assert.False(t, found)
assert.True(t, xslices.Equal(result, list))
assert.Equal(t, result, list)
result, found = findAndStrip([]string{}, "a")
assert.False(t, found)
assert.True(t, xslices.Equal(result, []string{}))
assert.Equal(t, result, []string{})
}
func TestFindAndStripWait(t *testing.T) {
result, found, values := findAndStripWait([]string{"a", "b", "c"})
assert.False(t, found)
assert.True(t, xslices.Equal(result, []string{"a", "b", "c"}))
assert.True(t, xslices.Equal(values, []string{}))
assert.Equal(t, result, []string{"a", "b", "c"})
assert.Equal(t, values, []string{})
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a"}))
assert.True(t, xslices.Equal(values, []string{"b"}))
assert.Equal(t, result, []string{"a"})
assert.Equal(t, values, []string{"b"})
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a"}))
assert.True(t, xslices.Equal(values, []string{"b", "c"}))
assert.Equal(t, result, []string{"a"})
assert.Equal(t, values, []string{"b", "c"})
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
assert.True(t, found)
assert.True(t, xslices.Equal(result, []string{"a"}))
assert.True(t, xslices.Equal(values, []string{"b", "c", "d"}))
assert.Equal(t, result, []string{"a"})
assert.Equal(t, values, []string{"b", "c", "d"})
}
func TestAppendOrModifySessionID(t *testing.T) {
sessionID := string(logging.NewSessionID())
assert.True(t, slices.Equal(appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID}))
assert.True(t, slices.Equal(appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID}))
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli"}, sessionID), []string{"--cli", "--session-id", sessionID}))
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID}))
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID}))
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--session-id", "<oldID>", "--cli"}, sessionID), []string{"--session-id", sessionID, "--cli"}))
assert.Equal(t, appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID})
assert.Equal(t, appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID})
assert.Equal(t, appendOrModifySessionID([]string{"--cli"}, sessionID), []string{"--cli", "--session-id", sessionID})
assert.Equal(t, appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID})
assert.Equal(t, appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID})
assert.Equal(t, appendOrModifySessionID([]string{"--session-id", "<oldID>", "--cli"}, sessionID), []string{"--session-id", sessionID, "--cli"})
}

View File

@ -15,113 +15,104 @@
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#include "Pch.h"
#include "CommandLine.h"
#include "Settings.h"
#include <bridgepp/CLI/CLIUtils.h>
#include <bridgepp/SessionID/SessionID.h>
using namespace bridgepp;
namespace {
QString const launcherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
QString const noWindowFlag = "--no-window"; ///< The no-window command-line flag.
QString const softwareRendererFlag = "--software-renderer"; ///< The 'software-renderer' command-line flag. enable software rendering for a single execution
QString const setSoftwareRendererFlag = "--set-software-renderer"; ///< The 'set-software-renderer' command-line flag. Software rendering will be used for all subsequent executions of the application.
QString const setHardwareRendererFlag = "--set-hardware-renderer"; ///< The 'set-hardware-renderer' command-line flag. Hardware rendering will be used for all subsequent executions of the application.
//****************************************************************************************************************************************************
/// \brief parse a command-line string argument as expected by go's CLI package.
/// \param[in] argc The number of arguments passed to the application.
/// \param[in] argv The list of arguments passed to the application.
/// \param[in] paramNames the list of names for the parameter
//****************************************************************************************************************************************************
QString parseGoCLIStringArgument(int argc, char *argv[], QStringList paramNames) {
// go cli package is pretty permissive when it comes to parsing arguments. For each name 'param', all the following seems to be accepted:
// -param value
// --param value
// -param=value
// --param=value
for (QString const &paramName: paramNames) {
for (qsizetype i = 1; i < argc; ++i) {
QString const arg(QString::fromLocal8Bit(argv[i]));
if ((i < argc - 1) && ((arg == "-" + paramName) || (arg == "--" + paramName))) {
return QString(argv[i + 1]);
}
QRegularExpressionMatch match = QRegularExpression(QString("^-{1,2}%1=(.+)$").arg(paramName)).match(arg);
if (match.hasMatch()) {
return match.captured(1);
}
}
}
return QString();
}
QString const hyphenatedLauncherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
QString const hyphenatedWindowFlag = "--no-window"; ///< The no-window command-line flag.
QString const hyphenatedSoftwareRendererFlag = "--software-renderer"; ///< The 'software-renderer' command-line flag. enable software rendering for a single execution
QString const hyphenatedSetSoftwareRendererFlag = "--set-software-renderer"; ///< The 'set-software-renderer' command-line flag. Software rendering will be used for all subsequent executions of the application.
QString const hyphenatedSetHardwareRendererFlag = "--set-hardware-renderer"; ///< The 'set-hardware-renderer' command-line flag. Hardware rendering will be used for all subsequent executions of the application.
QString const sessionIDFlag = "session-id";
QString const hyphenatedSessionIDFlag = "--" + sessionIDFlag;
//****************************************************************************************************************************************************
/// \brief Parse the log level from the command-line arguments.
///
/// \param[in] argc The number of arguments passed to the application.
/// \param[in] argv The list of arguments passed to the application.
/// \param[in] args The command-line arguments.
/// \return The log level. if not specified on the command-line, the default log level is returned.
//****************************************************************************************************************************************************
Log::Level parseLogLevel(int argc, char *argv[]) {
QString levelStr = parseGoCLIStringArgument(argc, argv, { "l", "log-level" });
Log::Level parseLogLevel(QStringList const &args) {
QStringList levelStr = parseGoCLIStringArgument(args, {"l", "log-level"});
if (levelStr.isEmpty()) {
return Log::defaultLevel;
}
Log::Level level = Log::defaultLevel;
Log::stringToLevel(levelStr, level);
Log::stringToLevel(levelStr.back(), level);
return level;
}
//****************************************************************************************************************************************************
/// \brief Return the most recent sessionID parsed in command-line arguments
///
/// \param[in] args The command-line arguments.
/// \return The most recent sessionID in the list. If the list is empty, a new sessionID is created.
//****************************************************************************************************************************************************
QString mostRecentSessionID(QStringList const& args) {
QStringList const sessionIDs = parseGoCLIStringArgument(args, {sessionIDFlag});
if (sessionIDs.isEmpty()) {
return newSessionID();
}
return std::ranges::max(sessionIDs, [](QString const &lhs, QString const &rhs) -> bool {
return sessionIDToDateTime(lhs) < sessionIDToDateTime(rhs);
});
}
} // anonymous namespace
//****************************************************************************************************************************************************
/// \param[in] argc number of arguments passed to the application.
/// \param[in] argv list of arguments passed to the application.
/// \param[in] argv list of arguments passed to the application, including the exe name/path at index 0.
/// \return The parsed options.
//****************************************************************************************************************************************************
CommandLineOptions parseCommandLine(int argc, char *argv[]) {
CommandLineOptions parseCommandLine(QStringList const &argv) {
CommandLineOptions options;
bool flagFound = false;
options.launcher = QString::fromLocal8Bit(argv[0]);
bool launcherFlagFound = false;
options.launcher = argv[0];
// for unknown reasons, on Windows QCoreApplication::arguments() frequently returns an empty list, which is incorrect, so we rebuild the argument
// list from the original argc and argv values.
for (int i = 1; i < argc; i++) {
QString const &arg = QString::fromLocal8Bit(argv[i]);
for (int i = 1; i < argv.count(); i++) {
QString const &arg = argv[i];
// we can't use QCommandLineParser here since it will fail on unknown options.
// we skip session-id for now we'll process it later, with a special treatment for duplicates
if (arg == hyphenatedSessionIDFlag) {
i++; // we skip the next param, which if the flag's value.
continue;
}
if (arg.startsWith(hyphenatedSessionIDFlag + "=")) {
continue;
}
// Arguments may contain some bridge flags.
if (arg == softwareRendererFlag) {
if (arg == hyphenatedSoftwareRendererFlag) {
options.bridgeGuiArgs.append(arg);
options.useSoftwareRenderer = true;
}
if (arg == setSoftwareRendererFlag) {
if (arg == hyphenatedSetSoftwareRendererFlag) {
app().settings().setUseSoftwareRenderer(true);
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
}
if (arg == setHardwareRendererFlag) {
if (arg == hyphenatedSetHardwareRendererFlag) {
app().settings().setUseSoftwareRenderer(false);
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
}
if (arg == noWindowFlag) {
if (arg == hyphenatedWindowFlag) {
options.noWindow = true;
}
if (arg == launcherFlag) {
if (arg == hyphenatedLauncherFlag) {
options.bridgeArgs.append(arg);
options.launcher = QString::fromLocal8Bit(argv[++i]);
options.launcher = argv[++i];
options.bridgeArgs.append(options.launcher);
flagFound = true;
launcherFlagFound = true;
}
#ifdef QT_DEBUG
else if (arg == "--attach" || arg == "-a") {
@ -135,22 +126,24 @@ CommandLineOptions parseCommandLine(int argc, char *argv[]) {
options.bridgeGuiArgs.append(arg);
}
}
if (!flagFound) {
if (!launcherFlagFound) {
// add bridge-gui as launcher
options.bridgeArgs.append(launcherFlag);
options.bridgeArgs.append(hyphenatedLauncherFlag);
options.bridgeArgs.append(options.launcher);
}
options.logLevel = parseLogLevel(argc, argv);
QString sessionID = parseGoCLIStringArgument(argc, argv, { "session-id" });
if (sessionID.isEmpty()) {
// The session ID was not passed to us on the command-line -> create one and add to the command-line for bridge
sessionID = newSessionID();
options.bridgeArgs.append("--session-id");
options.bridgeArgs.append(sessionID);
QStringList args;
if (!argv.isEmpty()) {
args = argv.last(argv.count() - 1);
}
options.logLevel = parseLogLevel(args);
QString const sessionID = mostRecentSessionID(args);
options.bridgeArgs.append(hyphenatedSessionIDFlag);
options.bridgeArgs.append(sessionID);
app().setSessionID(sessionID);
return options;
}

View File

@ -37,7 +37,7 @@ struct CommandLineOptions {
};
CommandLineOptions parseCommandLine(int argc, char *argv[]); ///< Parse the command-line arguments
CommandLineOptions parseCommandLine(QStringList const &argv); ///< Parse the command-line arguments
#endif //BRIDGE_GUI_COMMAND_LINE_H

View File

@ -15,7 +15,6 @@
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#include "BridgeApp.h"
#include "BuildConfig.h"
#include "CommandLine.h"
@ -30,13 +29,12 @@
#include <bridgepp/Log/LogUtils.h>
#include <bridgepp/ProcessMonitor.h>
#include "bridgepp/CLI/CLIUtils.h"
#ifdef Q_OS_MACOS
#include "MacOS/SecondInstance.h"
#endif
using namespace bridgepp;
@ -50,17 +48,14 @@ QString const exeSuffix = ".exe";
QString const exeSuffix;
#endif
QString const bridgeLock = "bridge-v3.lock"; ///< The file name used for the bridge-gui lock file.
QString const bridgeGUILock = "bridge-v3-gui.lock"; ///< The file name used for the bridge-gui lock file.
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
qint64 const grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
qint64 constexpr grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
QString const waitFlag = "--wait"; ///< The wait command-line flag.
} // anonymous namespace
//****************************************************************************************************************************************************
/// \return The path of the bridge executable.
/// \return A null string if the executable could not be located.
@ -70,7 +65,6 @@ QString locateBridgeExe() {
return (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) ? fileInfo.absoluteFilePath() : QString();
}
//****************************************************************************************************************************************************
/// // initialize the Qt application.
//****************************************************************************************************************************************************
@ -97,8 +91,6 @@ void initQtApplication() {
#endif // #ifdef Q_OS_MACOS
}
//****************************************************************************************************************************************************
/// \param[in] engine The QML component.
//****************************************************************************************************************************************************
@ -118,13 +110,12 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) {
rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml"));
if (rootComponent->status() != QQmlComponent::Status::Ready) {
QString const &err = rootComponent->errorString();
app().log().error(err);
app().log().error(err);
throw Exception("Could not load QML component", err);
}
return rootComponent;
}
//****************************************************************************************************************************************************
/// \param[in] lock The lock file to be checked.
/// \return True if the lock can be taken, false otherwise.
@ -155,7 +146,6 @@ bool checkSingleInstance(QLockFile &lock) {
return true;
}
//****************************************************************************************************************************************************
/// \return QUrl to reach the bridge API.
//****************************************************************************************************************************************************
@ -184,7 +174,6 @@ QUrl getApiUrl() {
return url;
}
//****************************************************************************************************************************************************
/// \brief Check if bridge is running.
///
@ -199,7 +188,6 @@ bool isBridgeRunning() {
return (!lockFile.tryLock()) && (lockFile.error() == QLockFile::LockFailedError);
}
//****************************************************************************************************************************************************
/// \brief Use api to bring focus on existing bridge instance.
//****************************************************************************************************************************************************
@ -213,8 +201,7 @@ void focusOtherInstance() {
if (!sc.load(path)) {
throw Exception("The gRPC focus service configuration file is invalid.");
}
}
else {
} else {
throw Exception("Server did not provide gRPC Focus service configuration.");
}
@ -225,20 +212,18 @@ void focusOtherInstance() {
if (!client.raise("focusOtherInstance").ok()) {
throw Exception(QString("The raise call to the bridge focus service failed."));
}
}
catch (Exception const &e) {
} catch (Exception const &e) {
app().log().error(e.qwhat());
auto uuid = reportSentryException("Exception occurred during focusOtherInstance()", e);
app().log().fatal(QString("reportID: %1 Captured exception: %2").arg(QByteArray(uuid.bytes, 16).toHex(), e.qwhat()));
}
}
//****************************************************************************************************************************************************
/// \param [in] args list of arguments to pass to bridge.
/// \return bridge executable path
//****************************************************************************************************************************************************
const QString launchBridge(QStringList const &args) {
QString launchBridge(QStringList const &args) {
UPOverseer &overseer = app().bridgeOverseer();
overseer.reset();
@ -251,26 +236,38 @@ const QString launchBridge(QStringList const &args) {
}
qint64 const pid = qApp->applicationPid();
QStringList const params = QStringList { "--grpc", "--parent-pid", QString::number(pid) } + args;
QStringList const params = QStringList{"--grpc", "--parent-pid", QString::number(pid)} + args;
app().log().info(QString("Launching bridge process with command \"%1\" %2").arg(bridgeExePath, params.join(" ")));
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, params, nullptr), nullptr);
overseer->startWorker(true);
return bridgeExePath;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void closeBridgeApp() {
app().grpc().quit(); // this will cause the grpc service and the bridge app to close.
UPOverseer &overseer = app().bridgeOverseer();
if (overseer) { // A null overseer means the app was run in 'attach' mode. We're not monitoring it.
UPOverseer const &overseer = app().bridgeOverseer();
if (overseer) {
// A null overseer means the app was run in 'attach' mode. We're not monitoring it.
// ReSharper disable once CppExpressionWithoutSideEffects
overseer->wait(Overseer::maxTerminationWaitTimeMs);
}
}
//****************************************************************************************************************************************************
/// \param[in] argv The command-line argments, including the application name at index 0.
//****************************************************************************************************************************************************
void logCommandLineInvocation(QStringList argv) {
Log &log = app().log();
if (argv.isEmpty()) {
log.error("The command line is empty");
}
log.info("bridge-gui executable: " + argv.front());
log.info("Command-line invocation: " + (argv.size() > 1 ? argv.last(argv.size() - 1).join(" ") : "<none>"));
}
//****************************************************************************************************************************************************
/// \param[in] argc The number of command-line arguments.
@ -289,12 +286,11 @@ int main(int argc, char *argv[]) {
auto sentryCloser = qScopeGuard([] { sentry_close(); });
try {
QString const& configDir = bridgepp::userConfigDir();
QString const &configDir = bridgepp::userConfigDir();
initQtApplication();
CommandLineOptions const cliOptions = parseCommandLine(argc, argv);
QStringList const argvList = cliArgsToStringList(argc, argv);
CommandLineOptions const cliOptions = parseCommandLine(argvList);
Log &log = initLog();
log.setLevel(cliOptions.logLevel);
@ -309,6 +305,8 @@ int main(int argc, char *argv[]) {
setDockIconVisibleState(!cliOptions.noWindow);
#endif
logCommandLineInvocation(argvList);
// In attached mode, we do not intercept stderr and stdout of bridge, as we did not launch it ourselves, so we output the log to the console.
// When not in attached mode, log entries are forwarded to bridge, which output it on stdout/stderr. bridge-gui's process monitor intercept
// these outputs and output them on the command-line.
@ -348,7 +346,6 @@ int main(int argc, char *argv[]) {
QQuickWindow::setSceneGraphBackend((app().settings().useSoftwareRenderer() || cliOptions.useSoftwareRenderer) ? "software" : "rhi");
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
QQmlApplicationEngine engine;
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext()));
@ -374,7 +371,7 @@ int main(int argc, char *argv[]) {
app().log().debug(QString("Monitoring Bridge PID : %1").arg(status.pid));
connection = QObject::connect(bridgeMonitor, &ProcessMonitor::processExited, [&](int returnCode) {
bridgeExited = true;// clazy:exclude=lambda-in-connect
bridgeExited = true; // clazy:exclude=lambda-in-connect
qGuiApp->exit(returnCode);
});
}
@ -383,7 +380,7 @@ int main(int argc, char *argv[]) {
int result = 0;
if (!startError) {
// we succeeded in launching bridge, so we can be set as mainExecutable.
QString mainexec = QString::fromLocal8Bit(argv[0]);
QString const mainexec = argvList[0];
app().grpc().setMainExecutable(mainexec);
QStringList args = cliOptions.bridgeGuiArgs;
args.append(waitFlag);
@ -412,8 +409,7 @@ int main(int argc, char *argv[]) {
// release the lock file
lock.unlock();
return result;
}
catch (Exception const &e) {
} catch (Exception const &e) {
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
QString message = e.qwhat();
if (e.showSupportLink()) {

View File

@ -23,16 +23,15 @@
using namespace bridgepp;
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
TEST(CLI, stripStringParameterFromCommandLine) {
struct Test {
struct TestData {
QStringList input;
QStringList expectedOutput;
};
QList<Test> const tests = {
QList<TestData> const tests = {
{{}, {}},
{{ "--a", "-b", "--C" }, { "--a", "-b", "--C" } },
{{ "--string", "value" }, {} },
@ -44,7 +43,36 @@ TEST(CLI, stripStringParameterFromCommandLine) {
{{ "--string", "--string", "value", "-b", "--string"}, { "value", "-b" } },
};
for (Test const& test: tests) {
for (TestData const& test: tests) {
EXPECT_EQ(stripStringParameterFromCommandLine("--string", test.input), test.expectedOutput);
}
}
TEST(CLI, parseGoCLIStringArgument) {
struct TestData {
QStringList args;
QStringList params;
QStringList expectedOutput;
};
QList<TestData> const tests = {
{ {}, {}, {} },
{ {"-param"}, {"param"}, {} },
{ {"--param", "1"}, {"param"}, { "1" } },
{ {"--param", "1","p", "-p", "2", "-flag", "-param=3", "--p=4"}, {"param", "p"}, { "1", "2", "3", "4" } },
{ {"--param", "--param", "1"}, {"param"}, { "--param" } },
};
for (TestData const& test: tests) {
EXPECT_EQ(parseGoCLIStringArgument(test.args, test.params), test.expectedOutput);
}
}
TEST(CLI, cliArgsToStringList) {
int constexpr argc = 3;
char *argv[] = { const_cast<char *>("1"), const_cast<char *>("2"), const_cast<char *>("3") };
QStringList const strList { "1", "2", "3" };
EXPECT_EQ(cliArgsToStringList(argc,argv), strList);
EXPECT_EQ(cliArgsToStringList(0, nullptr), QStringList {});
}

View File

@ -42,4 +42,51 @@ QStringList stripStringParameterFromCommandLine(QString const &paramName, QStrin
}
//****************************************************************************************************************************************************
/// The flags may be present more than once in the args. All values are returned in order of appearance.
///
/// \param[in] args The arguments
/// \param[in] paramNames the list of names for the parameter, without any prefix hypen.
/// \return The values found for the flag.
//****************************************************************************************************************************************************
QStringList parseGoCLIStringArgument(QStringList const &args, QStringList const& paramNames) {
// go cli package is pretty permissive when it comes to parsing arguments. For each name 'param', all the following seems to be accepted:
// -param value
// --param value
// -param=value
// --param=value
QStringList result;
qsizetype const argCount = args.count();
for (qsizetype i = 0; i < args.size(); ++i) {
for (QString const &paramName: paramNames) {
if ((i < argCount - 1) && ((args[i] == "-" + paramName) || (args[i] == "--" + paramName))) {
result.append(args[i + 1]);
i += 1;
continue;
}
if (QRegularExpressionMatch match = QRegularExpression(QString("^-{1,2}%1=(.+)$").arg(paramName)).match(args[i]); match.hasMatch()) {
result.append(match.captured(1));
continue;
}
}
}
return result;
}
//****************************************************************************************************************************************************
/// \param[in] argc The number of command-line arguments.
/// \param[in] argv The list of command-line arguments.
/// \return A QStringList representing the arguments list.
//****************************************************************************************************************************************************
QStringList cliArgsToStringList(int argc, char **argv) {
QStringList result;
result.reserve(argc);
for (qsizetype i = 0; i < argc; ++i) {
result.append(QString::fromLocal8Bit(argv[i]));
}
return result;
}
} // namespace bridgepp

View File

@ -15,18 +15,15 @@
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#ifndef BRIDGEPP_CLI_UTILS_H
#define BRIDGEPP_CLI_UTILS_H
namespace bridgepp {
QStringList stripStringParameterFromCommandLine(QString const &paramName, QStringList const &commandLineParams); ///< Remove a string parameter from a list of command-line parameters.
QStringList parseGoCLIStringArgument(QStringList const &args, QStringList const &paramNames); ///< Parse a command-line string argument as expected by go's CLI package.
QStringList cliArgsToStringList(int argc, char **argv); ///< Converts C-style command-line arguments to a string list.
}
#endif // BRIDGEPP_CLI_UTILS_H