From 0710a59bf567198b1b7bf82ea4a9e0f4820b5f00 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Fri, 13 May 2016 14:38:10 +0200 Subject: [PATCH] Refactor parameters parsing Introduce classes that encapsulate parameter names and parsing schemes from command line and from environment variables. --- src/app/options.cpp | 273 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 219 insertions(+), 54 deletions(-) diff --git a/src/app/options.cpp b/src/app/options.cpp index a714ff4cd..5cbf08b22 100644 --- a/src/app/options.cpp +++ b/src/app/options.cpp @@ -47,32 +47,205 @@ namespace { - bool isBoolEnvVarSetToTrue(const QProcessEnvironment &env, const QString &var) + // Base option class. Encapsulates name operations. + class Option { - QString val = env.value(var); - // we accept "1" and "true" (upper or lower cased) as boolean 'true' values - return (val == QLatin1String("1") || val.toUpper() == QLatin1String("TRUE")); + protected: + constexpr Option(const char *name, char shortcut = 0) + : m_name {name} + , m_shortcut {shortcut} + { + } + + QString fullParameter() const + { + return QLatin1String("--") + QLatin1String(m_name); + } + + QString shortcutParameter() const + { + return QLatin1String("-") + QLatin1Char(m_shortcut); + } + + bool hasShortcut() const + { + return m_shortcut != 0; + } + + QString envVarName() const + { + return QLatin1String("QBT_") + + QString(QLatin1String(m_name)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_')); + } + + static QString padUsageText(const QString &usage) + { + const int TAB_WIDTH = 8; + QString res = QLatin1String("\t") + usage; + if (usage.size() < 2 * TAB_WIDTH) + return res + QLatin1String("\t\t"); + else + return res + QLatin1String("\t"); + } + + private: + const char *m_name; + const char m_shortcut; + }; + + // Boolean option. + class BoolOption: protected Option + { + public: + constexpr BoolOption(const char *name, char shortcut = 0) + : Option {name, shortcut} + { + } + + bool operator==(const QString &arg) const + { + return (hasShortcut() && ((arg.size() == 2) && (arg == shortcutParameter()))) + || (arg == fullParameter()); + } + + bool value(const QProcessEnvironment &env) const + { + QString val = env.value(envVarName()); + // we accept "1" and "true" (upper or lower cased) as boolean 'true' values + return (val == QLatin1String("1") || val.toUpper() == QLatin1String("TRUE")); + } + + QString usage() const + { + QString res; + if (hasShortcut()) + res += shortcutParameter() + QLatin1String(" | "); + res += fullParameter(); + return padUsageText(res); + } + }; + + inline bool operator==(const QString &s, const BoolOption &o) + { + return o == s; } - int readIntlEnvVar(const QProcessEnvironment &env, const QString &var, int defaultValue) + // Option with string value. May not have a shortcut + struct StringOption: protected Option { - QString val = env.value(var); - if (val.isEmpty()) return defaultValue; - bool ok; - int res = val.toInt(&ok); - if (!ok) { - qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'") - .arg(var).arg(val); - return defaultValue; - } - return res; + public: + constexpr StringOption(const char *name) + : Option {name, 0} + { + } + + bool operator==(const QString &arg) const + { + return arg.startsWith(parameterAssignment()); + } + + QString value(const QString &arg) const + { + QStringList parts = arg.split(QLatin1Char('=')); + if (parts.size() == 2) + return unquote(parts[1]); + throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'", + "e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'") + .arg(fullParameter()).arg(QLatin1String(""))); + } + + QString value(const QProcessEnvironment &env, const QString &defaultValue = QString()) const + { + QString val = env.value(envVarName()); + return val.isEmpty() ? defaultValue : unquote(val); + } + + QString usage(const QString &valueName) const + { + return padUsageText(parameterAssignment() + QLatin1Char('<') + valueName + QLatin1Char('>')); + } + + private: + QString parameterAssignment() const + { + return fullParameter() + QLatin1Char('='); + } + + static QString unquote(const QString &s) + { + auto isStringQuoted = + [](const QString &s, QChar quoteChar) + { + return (s.startsWith(quoteChar) && s.endsWith(quoteChar)); + }; + + if ((s.size() >= 2) && (isStringQuoted(s, QLatin1Char('\'')) || isStringQuoted(s, QLatin1Char('"')))) + return s.mid(1, s.size() - 2); + return s; + } + }; + + inline bool operator==(const QString &s, const StringOption &o) + { + return o == s; } - QString envVarNameForParameter(const char *parameterName) + // Option with integer value. May not have a shortcut + class IntOption: protected StringOption { - return QLatin1String("QBT_") + - QString(QLatin1String(parameterName)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_')); + public: + constexpr IntOption(const char *name) + : StringOption {name} + { + } + + using StringOption::operator==; + using StringOption::usage; + + int value(const QString &arg) const + { + QString val = StringOption::value(arg); + bool ok = false; + int res = val.toInt(&ok); + if (!ok) + throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'", + "e.g. Parameter '--webui-port' must follow syntax '--webui-port='") + .arg(fullParameter()).arg(QLatin1String(""))); + return res; + } + + int value(const QProcessEnvironment &env, int defaultValue) const + { + QString val = env.value(envVarName()); + if (val.isEmpty()) return defaultValue; + + bool ok; + int res = val.toInt(&ok); + if (!ok) { + qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'") + .arg(envVarName()).arg(val); + return defaultValue; + } + return res; + } + }; + + inline bool operator==(const QString &s, const IntOption &o) + { + return o == s; } + + constexpr const BoolOption SHOW_HELP_OPTION = {"help", 'h'}; + constexpr const BoolOption SHOW_VERSION_OPTION = {"version", 'v'}; +#ifdef DISABLE_GUI + constexpr const BoolOption DAEMON_OPTION = {"daemon", 'd'}; +#else + constexpr const BoolOption NO_SPLASH_OPTION = {"no-splash"}; +#endif + constexpr const IntOption WEBUI_PORT_OPTION = {"webui-port"}; + constexpr const StringOption PROFILE_OPTION = {"profile"}; + constexpr const StringOption CONFIGURATION_OPTION = {"configuration"}; + constexpr const BoolOption PORTABLE_OPTION = {"portable"}; } QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env) @@ -81,14 +254,14 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en , showVersion(false) #endif #ifndef DISABLE_GUI - , noSplash(isBoolEnvVarSetToTrue(env, envVarNameForParameter("no-splash"))) + , noSplash(NO_SPLASH_OPTION.value(env)) #else - , shouldDaemonize(isBoolEnvVarSetToTrue(env, envVarNameForParameter("daemon"))) + , shouldDaemonize(DAEMON_OPTION.value(env)) #endif - , webUiPort(readIntlEnvVar(env, envVarNameForParameter("webui-port"), -1)) - , profileDir(env.value(envVarNameForParameter("profile"))) - , portableMode(isBoolEnvVarSetToTrue(env, envVarNameForParameter("portable"))) - , configurationName(env.value(envVarNameForParameter("configuration"))) + , webUiPort(WEBUI_PORT_OPTION.value(env, -1)) + , profileDir(PROFILE_OPTION.value(env)) + , portableMode(PORTABLE_OPTION.value(env)) + , configurationName(CONFIGURATION_OPTION.value(env)) { } @@ -102,45 +275,37 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args) if ((arg.startsWith("--") && !arg.endsWith(".torrent")) || (arg.startsWith("-") && (arg.size() == 2))) { // Parse known parameters - if ((arg == QLatin1String("-h")) || (arg == QLatin1String("--help"))) { + if ((arg == SHOW_HELP_OPTION)) { result.showHelp = true; } #ifndef Q_OS_WIN - else if ((arg == QLatin1String("-v")) || (arg == QLatin1String("--version"))) { + else if (arg == SHOW_VERSION_OPTION) { result.showVersion = true; } #endif - else if (arg.startsWith(QLatin1String("--webui-port="))) { - QStringList parts = arg.split(QLatin1Char('=')); - if (parts.size() == 2) { - bool ok = false; - result.webUiPort = parts.last().toInt(&ok); - if (!ok || (result.webUiPort < 1) || (result.webUiPort > 65535)) - throw CommandLineParameterError(QObject::tr("%1 must specify the correct port (1 to 65535).") - .arg(QLatin1String("--webui-port"))); - } + else if (arg == WEBUI_PORT_OPTION) { + result.webUiPort = WEBUI_PORT_OPTION.value(arg); + if ((result.webUiPort < 1) || (result.webUiPort > 65535)) + throw CommandLineParameterError(QObject::tr("%1 must specify the correct port (1 to 65535).") + .arg(QLatin1String("--webui-port"))); } #ifndef DISABLE_GUI - else if (arg == QLatin1String("--no-splash")) { + else if (arg == NO_SPLASH_OPTION) { result.noSplash = true; } #else - else if ((arg == QLatin1String("-d")) || (arg == QLatin1String("--daemon"))) { + else if (arg == DAEMON_OPTION) { result.shouldDaemonize = true; } #endif - else if (arg == QLatin1String("--profile")) { - QStringList parts = arg.split(QLatin1Char('=')); - if (parts.size() == 2) - result.profileDir = parts.last(); + else if (arg == PROFILE_OPTION) { + result.profileDir = PROFILE_OPTION.value(arg); } - else if (arg == QLatin1String("--portable")) { + else if (arg == PORTABLE_OPTION) { result.portableMode = true; } - else if (arg == QLatin1String("--configuration")) { - QStringList parts = arg.split(QLatin1Char('=')); - if (parts.size() == 2) - result.configurationName = parts.last(); + else if (arg == CONFIGURATION_OPTION) { + result.configurationName = CONFIGURATION_OPTION.value(arg); } else { // Unknown argument @@ -185,20 +350,20 @@ QString makeUsage(const QString &prgName) stream << QObject::tr("Options:") << '\n'; #ifndef Q_OS_WIN - stream << "\t-v | --version\t\t" << QObject::tr("Displays program version and exit") << '\n'; + stream << SHOW_VERSION_OPTION.usage() << QObject::tr("Displays program version and exit") << '\n'; #endif - stream << "\t-h | --help\t\t" << QObject::tr("Displays this help message and exit") << '\n'; - stream << "\t--webui-port=\t" + stream << SHOW_HELP_OPTION.usage() << QObject::tr("Displays this help message and exit") << '\n'; + stream << WEBUI_PORT_OPTION.usage(QLatin1String("port")) << QObject::tr("Changes the Web UI port") << '\n'; #ifndef DISABLE_GUI - stream << "\t--no-splash\t\t" << QObject::tr("Disable splash screen") << '\n'; + stream << NO_SPLASH_OPTION.usage() << QObject::tr("Disable splash screen") << '\n'; #else - stream << "\t-d | --daemon\t\t" << QObject::tr("Run in daemon-mode (background)") << '\n'; + stream << DAEMON_OPTION.usage() << QObject::tr("Run in daemon-mode (background)") << '\n'; #endif - stream << "\t--profile=\t\t" << QObject::tr("Store configuration files in ") << '\n'; - stream << "\t--portable\t\t" << QObject::tr("Shortcut for --profile=/profile") << '\n'; - stream << "\t--configuration=\t" << QObject::tr("Store configuration files in directories qBittorrent_") + stream << PROFILE_OPTION.usage(QLatin1String("dir")) << QObject::tr("Store configuration files in ") << '\n'; + stream << PORTABLE_OPTION.usage() << QObject::tr("Shortcut for --profile=/profile") << '\n'; + stream << CONFIGURATION_OPTION.usage(QLatin1String("name")) << QObject::tr("Store configuration files in directories qBittorrent_") << '\n'; stream << "\tfiles or urls\t\t" << QObject::tr("Downloads the torrents passed by the user") << '\n' << '\n';