implement commandline parsing

This commit is contained in:
Orochimarufan 2013-02-20 00:07:52 +01:00
parent cf0e78b46d
commit c523a2c752
5 changed files with 764 additions and 7 deletions

View File

@ -117,6 +117,7 @@ gui/browserdialog.cpp
util/pathutils.cpp
util/osutils.cpp
util/userutil.cpp
util/cmdutils.cpp
java/javautils.cpp
java/annotations.cpp
@ -147,6 +148,7 @@ util/apputils.h
util/pathutils.h
util/osutils.h
util/userutil.h
util/cmdutils.h
multimc_pragma.h

View File

@ -1,4 +1,8 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Andrew Okin
* Peterix
* Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,9 +23,10 @@
#include <QMenu>
#include <QMessageBox>
#include <QInputDialog>
#include <QApplication>
#include <QDesktopServices>
#include <QUrl>
#include <QDir>
#include "util/osutils.h"
#include "util/userutil.h"
@ -168,9 +173,9 @@ void MainWindow::on_actionMakeDesktopShortcut_triggered()
QString name("Test");
name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."), QLineEdit::Normal, name);
Util::createShortCut(Util::getDesktopDir(), "test", QStringList() << "-d" << "lol", name, "application-x-octet-stream");
Util::createShortCut(Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), QStringList() << "-dl" << QDir::currentPath() << "test", name, "application-x-octet-stream");
QMessageBox::warning(this, "Stupidness", "A Dummy Shortcut was created. the current instance model doesnt allow for anything more");
QMessageBox::warning(this, "Not useful", "A Dummy Shortcut was created. it will not do anything productive");
}
// BrowserDialog

View File

@ -1,5 +1,6 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,26 +15,105 @@
* limitations under the License.
*/
#include "gui/mainwindow.h"
#include <iostream>
#include <QApplication>
#include <QDir>
#include "gui/mainwindow.h"
#include "data/appsettings.h"
#include "data/loginresponse.h"
#include "util/cmdutils.h"
using namespace Util::Commandline;
int main(int argc, char *argv[])
{
// initialize Qt
QApplication app(argc, argv);
app.setOrganizationName("Forkk");
app.setApplicationName("MultiMC 5");
// Print app header
std::cout << "MultiMC 5" << std::endl;
std::cout << "(c) 2013 MultiMC contributors" << std::endl << std::endl;
// Commandline parsing
Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals);
// --help
parser.addSwitch("help");
parser.addShortOpt("help", 'h');
parser.addDocumentation("help", "displays help on command line parameters");
// --dir
parser.addOption("dir", app.applicationDirPath());
parser.addShortOpt("dir", 'd');
parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of the binary location (use '.' for current)");
// --update
parser.addOption("update");
parser.addShortOpt("update", 'u');
parser.addDocumentation("update", "replaces the given file with the running executable", "<path>");
// --quietupdate
parser.addSwitch("quietupdate");
parser.addShortOpt("quietupdate", 'U');
parser.addDocumentation("quietupdate", "doesn't restart MultiMC after installing updates");
// --launch
parser.addOption("launch");
parser.addShortOpt("launch", 'l');
parser.addDocumentation("launch", "tries to launch the given instance", "<inst>");
// parse the arguments
QHash<QString, QVariant> args;
try {
args = parser.parse(app.arguments());
} catch(ParsingError e) {
std::cerr << "CommandLineError: " << e.what() << std::endl;
return 1;
}
// display help and exit
if (args["help"].toBool()) {
std::cout << qPrintable(parser.compileHelp(app.arguments()[0]));
return 0;
}
// update
// Note: cwd is always the current executable path!
if (!args["update"].isNull())
{
std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl;
QDir::setCurrent(app.applicationDirPath());
QFile file(app.applicationFilePath());
file.copy(args["update"].toString());
if(args["quietupdate"].toBool())
return 0;
}
// change directory
QDir::setCurrent(args["dir"].toString());
// launch instance.
if (!args["launch"].isNull())
{
std::cout << "Launching instance: " << qPrintable(args["launch"].toString()) << std::endl;
// TODO: make it launch the an instance.
// needs the new instance model to be complete
std::cerr << "Launching Instances is not implemented yet!" << std::endl;
return 255;
}
// load settings
settings = new AppSettings(&app);
// Register meta types.
qRegisterMetaType<LoginResponse>("LoginResponse");
// show window
MainWindow mainWin;
mainWin.show();
// loop
return app.exec();
}

433
util/cmdutils.cpp Normal file
View File

@ -0,0 +1,433 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cmdutils.h"
/**
* @file utils/cmdutils.cpp
*/
namespace Util {
namespace Commandline {
Parser::Parser(FlagStyle flagStyle, ArgumentStyle argStyle)
{
m_flagStyle = flagStyle;
m_argStyle = argStyle;
}
// styles setter/getter
void Parser::setArgumentStyle(ArgumentStyle style)
{
m_argStyle = style;
}
ArgumentStyle Parser::argumentStyle()
{
return m_argStyle;
}
void Parser::setFlagStyle(FlagStyle style)
{
m_flagStyle = style;
}
FlagStyle Parser::flagStyle()
{
return m_flagStyle;
}
// setup methods
void Parser::addSwitch(QString name, bool def) throw (const char *)
{
if (m_params.contains(name))
throw "Name not unique";
OptionDef *param = new OptionDef;
param->type = OptionType::Switch;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
m_options[name] = param;
m_params[name] = (CommonDef *)param;
m_optionList.append(param);
}
void Parser::addOption(QString name, QVariant def) throw (const char *)
{
if (m_params.contains(name))
throw "Name not unique";
OptionDef *param = new OptionDef;
param->type = OptionType::Option;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
m_options[name] = param;
m_params[name] = (CommonDef *)param;
m_optionList.append(param);
}
void Parser::addArgument(QString name, bool required, QVariant def) throw (const char *)
{
if (m_params.contains(name))
throw "Name not unique";
PositionalDef *param = new PositionalDef;
param->name = name;
param->def = def;
param->required = required;
m_positionals.append(param);
m_params[name] = (CommonDef *)param;
}
void Parser::addDocumentation(QString name, QString doc, QString metavar) throw (const char *)
{
if (!m_params.contains(name))
throw "Name does not exist";
CommonDef *param = m_params[name];
param->doc = doc;
if (!metavar.isNull())
param->metavar = metavar;
}
void Parser::addShortOpt(QString name, QChar flag) throw (const char *)
{
if (!m_params.contains(name))
throw "Name does not exist";
if (!m_options.contains(name))
throw "Name is not an Option or Swtich";
OptionDef *param = m_options[name];
m_flags[flag] = param;
param->flag = flag;
}
// help methods
QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
{
QStringList help;
help << compileUsage(progName, useFlags) << "\r\n";
// positionals
if (!m_positionals.isEmpty())
{
help << "\r\n";
help << "Positional arguments:\r\n";
QListIterator<PositionalDef *> it2(m_positionals);
while(it2.hasNext())
{
PositionalDef *param = it2.next();
help << " " << param->metavar;
help << " " << QString(helpIndent - param->name.length() - 1, ' ');
help << param->doc << "\r\n";
}
}
// Options
if (!m_optionList.isEmpty())
{
help << "\r\n";
QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix);
help << "Options & Switches:\r\n";
QListIterator<OptionDef *> it(m_optionList);
while(it.hasNext())
{
OptionDef *option = it.next();
help << " ";
int nameLength = optPrefix.length() + option->name.length();
if (!option->flag.isNull())
{
nameLength += 3 + flagPrefix.length();
help << flagPrefix << option->flag << ", ";
}
help << optPrefix << option->name;
if (option->type == OptionType::Option)
{
QString arg = QString("%1%2").arg(((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
nameLength += arg.length();
help << arg;
}
help << " " << QString(helpIndent - nameLength - 1, ' ');
help << option->doc << "\r\n";
}
}
return help.join("");
}
QString Parser::compileUsage(QString progName, bool useFlags)
{
QStringList usage;
usage << "Usage: " << progName;
QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix);
// options
QListIterator<OptionDef *> it(m_optionList);
while(it.hasNext())
{
OptionDef *option = it.next();
usage << " [";
if (!option->flag.isNull() && useFlags)
usage << flagPrefix << option->flag;
else
usage << optPrefix << option->name;
if (option->type == OptionType::Option)
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
usage << "]";
}
// arguments
QListIterator<PositionalDef *> it2(m_positionals);
while(it2.hasNext())
{
PositionalDef *param = it2.next();
usage << " " << (param->required ? "<" : "[");
usage << param->name;
usage << (param->required ? ">" : "]");
}
return usage.join("");
}
// parsing
QHash<QString, QVariant> Parser::parse(QStringList argv) throw (ParsingError)
{
QHash<QString, QVariant> map;
QStringListIterator it(argv);
QString programName = it.next();
QString optionPrefix;
QString flagPrefix;
QListIterator<PositionalDef *> positionals(m_positionals);
QStringList expecting;
getPrefix(optionPrefix, flagPrefix);
while (it.hasNext())
{
QString arg = it.next();
if (!expecting.isEmpty())
// we were expecting an argument
{
QString name = expecting.first();
if (map.contains(name))
throw ParsingError(QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
map[name] = QVariant(arg);
expecting.removeFirst();
continue;
}
if (arg.startsWith(optionPrefix))
// we have an option
{
//qDebug("Found option %s", qPrintable(arg));
QString name = arg.mid(optionPrefix.length());
QString equals;
if ((m_argStyle == ArgumentStyle::Equals || m_argStyle == ArgumentStyle::SpaceAndEquals) && name.contains("="))
{
int i = name.indexOf("=");
equals = name.mid(i+1);
name = name.left(i);
}
if (m_options.contains(name))
{
if (map.contains(name))
throw ParsingError(QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
OptionDef *option = m_options[name];
if (option->type == OptionType::Switch)
map[name] = true;
else //if (option->type == OptionType::Option)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(name);
else if (!equals.isNull())
map[name] = equals;
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(name);
else
throw ParsingError(QString("Option %2%1 reqires an argument.").arg(name, optionPrefix));
}
continue;
}
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
}
if (arg.startsWith(flagPrefix))
// we have (a) flag(s)
{
//qDebug("Found flags %s", qPrintable(arg));
QString flags = arg.mid(flagPrefix.length());
QString equals;
if ((m_argStyle == ArgumentStyle::Equals || m_argStyle == ArgumentStyle::SpaceAndEquals) && flags.contains("="))
{
int i = flags.indexOf("=");
equals = flags.mid(i+1);
flags = flags.left(i);
}
for (int i = 0; i < flags.length(); i++)
{
QChar flag = flags.at(i);
if (!m_flags.contains(flag))
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
OptionDef *option = m_flags[flag];
if (map.contains(option->name))
throw ParsingError(QString("Option %2%1 was given multiple times").arg(option->name, optionPrefix));
if (option->type == OptionType::Switch)
map[option->name] = true;
else //if (option->type == OptionType::Option)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(option->name);
else if (!equals.isNull())
if (i == flags.length()-1)
map[option->name] = equals;
else
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option %1 not last flag in %4%3").arg(option->name, flag, flags, flagPrefix));
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(option->name);
else
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)").arg(option->name, flag, flagPrefix));
}
}
continue;
}
// must be a positional argument
if (!positionals.hasNext())
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
PositionalDef *param = positionals.next();
map[param->name] = arg;
}
// check if we're missing something
if (!expecting.isEmpty())
throw ParsingError(QString("Was still expecting arguments for %2%1").arg(expecting.join(QString(", ")+optionPrefix), optionPrefix));
while (positionals.hasNext())
{
PositionalDef *param = positionals.next();
if (param->required)
throw ParsingError(QString("Missing required positional argument '%1'").arg(param->name));
else
map[param->name] = param->def;
}
// fill out gaps
QListIterator<OptionDef *> iter(m_optionList);
while (iter.hasNext())
{
OptionDef *option = iter.next();
if (!map.contains(option->name))
map[option->name] = option->def;
}
return map;
}
//clear defs
void Parser::clear()
{
m_flags.clear();
m_params.clear();
m_options.clear();
QMutableListIterator<OptionDef *> it(m_optionList);
while(it.hasNext())
{
OptionDef *option = it.next();
it.remove();
delete option;
}
QMutableListIterator<PositionalDef *> it2(m_positionals);
while(it2.hasNext())
{
PositionalDef *arg = it2.next();
it2.remove();
delete arg;
}
}
//Destructor
Parser::~Parser()
{
clear();
}
//getPrefix
void Parser::getPrefix(QString &opt, QString &flag)
{
if (m_flagStyle == FlagStyle::Windows)
opt = flag = "/";
else if (m_flagStyle == FlagStyle::Unix)
opt = flag = "-";
//else if (m_flagStyle == FlagStyle::GNU)
else {
opt = "--";
flag = "-";
}
}
// ParsingError
ParsingError::ParsingError(const QString &what) throw()
{
m_what = what;
}
ParsingError::ParsingError(const ParsingError &e) throw()
{
m_what = e.m_what;
}
char *ParsingError::what() const throw()
{
return m_what.toLocal8Bit().data();
}
QString ParsingError::qwhat() const throw()
{
return m_what;
}
}
}

237
util/cmdutils.h Normal file
View File

@ -0,0 +1,237 @@
/* Copyright 2013 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CMDUTILS_H
#define CMDUTILS_H
#include <exception>
#include <QString>
#include <QVariant>
#include <QHash>
#include <QStringList>
/**
* @file utils/cmdutils.h
* @brief commandline parsing utilities
*/
namespace Util {
namespace Commandline {
/**
* @brief The FlagStyle enum
* Specifies how flags are decorated
*/
enum class FlagStyle {
GNU, /**< --option and -o (GNU Style) */
Unix, /**< -option and -o (Unix Style) */
Windows, /**< /option and /o (Windows Style) */
#ifdef Q_OS_WIN32
Default = Windows
#else
Default = GNU
#endif
};
/**
* @brief The ArgumentStyle enum
*/
enum class ArgumentStyle {
Space, /**< --option=value */
Equals, /**< --option value */
SpaceAndEquals, /**< --option[= ]value */
#ifdef Q_OS_WIN32
Default = Equals
#else
Default = SpaceAndEquals
#endif
};
/**
* @brief The ParsingError class
*/
class ParsingError : public std::exception
{
public:
ParsingError(const QString &what) throw();
ParsingError(const ParsingError &e) throw();
~ParsingError() throw() {}
char *what() const throw();
QString qwhat() const throw();
private:
QString m_what;
};
/**
* @brief The Parser class
*/
class Parser
{
public:
/**
* @brief Parser constructor
* @param flagStyle the FlagStyle to use in this Parser
* @param argStyle the ArgumentStyle to use in this Parser
*/
Parser(FlagStyle flagStyle=FlagStyle::Default, ArgumentStyle argStyle=ArgumentStyle::Default);
/**
* @brief set the flag style
* @param style
*/
void setFlagStyle(FlagStyle style);
/**
* @brief get the flag style
* @return
*/
FlagStyle flagStyle();
/**
* @brief set the argument style
* @param style
*/
void setArgumentStyle(ArgumentStyle style);
/**
* @brief get the argument style
* @return
*/
ArgumentStyle argumentStyle();
/**
* @brief define a boolean switch
* @param name the parameter name
* @param def the default value
*/
void addSwitch(QString name, bool def=false) throw (const char *);
/**
* @brief define an option that takes an additional argument
* @param name the parameter name
* @param def the default value
*/
void addOption(QString name, QVariant def=QVariant()) throw (const char *);
/**
* @brief define a positional argument
* @param name the parameter name
* @param required wether this argument is required
* @param def the default value
*/
void addArgument(QString name, bool required=true, QVariant def=QVariant()) throw (const char *);
/**
* @brief adds a flag to an existing parameter
* @param name the (existing) parameter name
* @param flag the flag character
* @see addSwitch addArgument addOption
* Note: any one parameter can only have one flag
*/
void addShortOpt(QString name, QChar flag) throw (const char *);
/**
* @brief adds documentation to a Parameter
* @param name the parameter name
* @param metavar a string to be displayed as placeholder for the value
* @param doc a QString containing the documentation
*/
void addDocumentation(QString name, QString doc, QString metavar=QString()) throw (const char *);
/**
* @brief generate a help message
* @param progName the program name to use in the help message
* @param helpIndent how much the parameter documentation should be indented
* @param flagsInUsage whether we should use flags instead of options in the usage
* @return a help message
*/
QString compileHelp(QString progName, int helpIndent=22, bool flagsInUsage=true);
/**
* @brief generate a short usage message
* @param progName the program name to use in the usage message
* @param useFlags whether we should use flags instead of options
* @return a usage message
*/
QString compileUsage(QString progName, bool useFlags=true);
/**
* @brief parse
* @param argv a QStringList containing the program ARGV
* @return a QHash mapping argument names to their values
*/
QHash<QString, QVariant> parse(QStringList argv) throw (ParsingError);
/**
* @brief clear all definitions
*/
void clear();
~Parser();
private:
FlagStyle m_flagStyle;
ArgumentStyle m_argStyle;
enum class OptionType {
Switch,
Option
};
// Important: the common part MUST BE COMMON ON ALL THREE structs
struct CommonDef {
QString name;
QString doc;
QString metavar;
QVariant def;
};
struct OptionDef {
// common
QString name;
QString doc;
QString metavar;
QVariant def;
// option
OptionType type;
QChar flag;
};
struct PositionalDef {
// common
QString name;
QString doc;
QString metavar;
QVariant def;
// positional
bool required;
};
QHash<QString, OptionDef *> m_options;
QHash<QChar, OptionDef *> m_flags;
QHash<QString, CommonDef *> m_params;
QList<PositionalDef *> m_positionals;
QList<OptionDef *> m_optionList;
void getPrefix(QString &opt, QString &flag);
};
}
}
#endif // CMDUTILS_H