Refactor parameters parsing

Introduce classes that encapsulate parameter names and parsing schemes
from command line and from environment variables.
This commit is contained in:
Eugene Shalygin 2016-05-13 14:38:10 +02:00
parent 0bf7fa15c1
commit 0710a59bf5

View File

@ -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"));
}
int readIntlEnvVar(const QProcessEnvironment &env, const QString &var, int defaultValue)
{
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;
protected:
constexpr Option(const char *name, char shortcut = 0)
: m_name {name}
, m_shortcut {shortcut}
{
}
return res;
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;
}
QString envVarNameForParameter(const char *parameterName)
// Option with string value. May not have a shortcut
struct StringOption: protected Option
{
return QLatin1String("QBT_") +
QString(QLatin1String(parameterName)).toUpper().replace(QLatin1Char('-'), QLatin1Char('_'));
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("<value>")));
}
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;
}
// Option with integer value. May not have a shortcut
class IntOption: protected StringOption
{
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=<value>'")
.arg(fullParameter()).arg(QLatin1String("<integer value>")));
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=<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=<dir>\t\t" << QObject::tr("Store configuration files in <dir>") << '\n';
stream << "\t--portable\t\t" << QObject::tr("Shortcut for --profile=<exe dir>/profile") << '\n';
stream << "\t--configuration=<name>\t" << QObject::tr("Store configuration files in directories qBittorrent_<name>")
stream << PROFILE_OPTION.usage(QLatin1String("dir")) << QObject::tr("Store configuration files in <dir>") << '\n';
stream << PORTABLE_OPTION.usage() << QObject::tr("Shortcut for --profile=<exe dir>/profile") << '\n';
stream << CONFIGURATION_OPTION.usage(QLatin1String("name")) << QObject::tr("Store configuration files in directories qBittorrent_<name>")
<< '\n';
stream << "\tfiles or urls\t\t" << QObject::tr("Downloads the torrents passed by the user") << '\n'
<< '\n';