diff options
-rw-r--r-- | src/CommandLineParser.cpp | 400 | ||||
-rw-r--r-- | src/CommandLineParser.h | 98 | ||||
-rw-r--r-- | src/main.cpp | 58 | ||||
-rw-r--r-- | src/options.cpp | 125 | ||||
-rw-r--r-- | src/options.h | 44 | ||||
-rw-r--r-- | src/repository.cpp | 6 | ||||
-rw-r--r-- | src/src.pro | 12 |
7 files changed, 559 insertions, 184 deletions
diff --git a/src/CommandLineParser.cpp b/src/CommandLineParser.cpp new file mode 100644 index 0000000..62f0f39 --- /dev/null +++ b/src/CommandLineParser.cpp @@ -0,0 +1,400 @@ +/* + * This file is part of the vng project + * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "CommandLineParser.h" + +#include <QDebug> +#include <QTextStream> +#include <QStringList> +#include <QList> +#include <QHash> + +CommandLineParser *CommandLineParser::self = 0; + +class CommandLineParser::Private +{ +public: + Private(int argc, char **argv); + + // functions + void addDefinitions(const CommandLineOption * options); + void setArgumentDefinition(const char *definition); + void parse(); + + // variables; + const int argumentCount; + char ** const argumentStrings; + bool dirty; + int requiredArguments; + QString argumentDefinition; + + struct OptionDefinition { + OptionDefinition() : optionalParameters(0), requiredParameters(0) { } + QString name; + QString comment; + QChar shortName; + int optionalParameters; + int requiredParameters; + }; + + // result of what the user typed + struct ParsedOption { + QString option; + QList<QString> parameters; + }; + + QList<OptionDefinition> definitions; + QHash<QString, ParsedOption> options; + QList<QString> arguments; + QList<QString> undefinedOptions; + QList<QString> errors; +}; + + +CommandLineParser::Private::Private(int argc, char **argv) + : argumentCount(argc), argumentStrings(argv), dirty(true), + requiredArguments(0) +{ +} + +void CommandLineParser::Private::addDefinitions(const CommandLineOption * options) +{ + for (int i=0; options[i].specification != 0; i++) { + OptionDefinition definition; + QString option = QString::fromLatin1(options[i].specification); + if (option.indexOf(QLatin1Char(',')) >= 0) { + QStringList optionParts = option.split(QLatin1Char(','), QString::SkipEmptyParts); + if (optionParts.count() != 2) { + qWarning() << "option definition '" << option << "' is faulty; only one ',' allowed"; + continue; + } + foreach (QString s, optionParts) { + s = s.trimmed(); + if (s.startsWith(QLatin1String("--")) && s.length() > 2) + definition.name = s.mid(2); + else if (s.startsWith(QLatin1String("-")) && s.length() > 1) + definition.shortName = s.at(1); + else { + qWarning() << "option definition '" << option << "' is faulty; the option should start with a -"; + break; + } + } + } + else if (option.startsWith(QLatin1String("--")) && option.length() > 2) + definition.name = option.mid(2); + else + qWarning() << "option definition '" << option << "' has unrecognized format. See the api docs for CommandLineParser for a howto"; + + if(definition.name.isEmpty()) + continue; + if (option.indexOf(QLatin1Char(' ')) > 0) { + QStringList optionParts = definition.name.split(QLatin1Char(' '), QString::SkipEmptyParts); + definition.name = optionParts[0]; + bool first = true; + foreach (QString s, optionParts) { + if (first) { + first = false; + continue; + } + s = s.trimmed(); + if (s[0].unicode() == '[' && s.endsWith(QLatin1Char(']'))) + definition.optionalParameters++; + else + definition.requiredParameters++; + } + } + + definition.comment = QString::fromLatin1(options[i].description); + definitions << definition; + } +/* + foreach (OptionDefinition def, definitions) { + qDebug() << "definition:" << (def.shortName != 0 ? def.shortName : QChar(32)) << "|" << def.name << "|" << def.comment + << "|" << def.requiredParameters << "+" << def.optionalParameters; + } +*/ + + dirty = true; +} + +void CommandLineParser::Private::setArgumentDefinition(const char *defs) +{ + requiredArguments = 0; + argumentDefinition = QString::fromLatin1(defs); + QStringList optionParts = argumentDefinition.split(QLatin1Char(' '), QString::SkipEmptyParts); + bool inArg = false; + foreach (QString s, optionParts) { + s = s.trimmed(); + if (s[0].unicode() == '<') { + inArg = true; + requiredArguments++; + } + else if (s[0].unicode() == '[') + inArg = true; + if (s.endsWith(QLatin1Char('>'))) + inArg = false; + else if (!inArg) + requiredArguments++; + } +} + +void CommandLineParser::Private::parse() +{ + if (dirty == false) + return; + errors.clear(); + options.clear(); + arguments.clear(); + undefinedOptions.clear(); + dirty = false; + + class OptionProcessor { + public: + OptionProcessor(Private *d) : clp(d) { } + + void next(Private::ParsedOption &option) { + if (! option.option.isEmpty()) { + // find the definition to match. + OptionDefinition def; + foreach (Private::OptionDefinition definition, clp->definitions) { + if (definition.name == option.option) { + def = definition; + break; + } + } + if (! def.name.isEmpty() && def.requiredParameters >= option.parameters.count() && + def.requiredParameters + def.optionalParameters <= option.parameters.count()) + clp->options.insert(option.option, option); + else if (!clp->undefinedOptions.contains(option.option)) + clp->undefinedOptions << option.option; + else + clp->errors.append(QLatin1String("Not enough arguments passed for option `") + + option.option +QLatin1Char('\'')); + } + option.option.clear(); + option.parameters.clear(); + } + + private: + CommandLineParser::Private *clp; + }; + OptionProcessor processor(this); + + bool optionsAllowed = true; + ParsedOption option; + OptionDefinition currentDefinition; + for(int i = 1; i < argumentCount; i++) { + QString arg = QString::fromLocal8Bit(argumentStrings[i]); + if (optionsAllowed) { + if (arg == QLatin1String("--")) { + optionsAllowed = false; + continue; + } + if (arg.startsWith(QLatin1String("--"))) { + processor.next(option); + int end = arg.indexOf(QLatin1Char('=')); + option.option = arg.mid(2, end - 2); + if (end > 0) + option.parameters << arg.mid(end+1); + continue; + } + if (arg[0].unicode() == '-' && arg.length() > 1) { + for(int x = 1; x < arg.length(); x++) { + foreach (OptionDefinition definition, definitions) { + if (definition.shortName == arg[x]) { + processor.next(option); + currentDefinition = definition; + option.option = definition.name; + + if (definition.requiredParameters == 1 && arg.length() >= x+2) { + option.parameters << arg.mid(x+1, arg.length()); + x = arg.length(); + } + break; + } + } + if (option.option.isEmpty()) { // nothing found; copy char so it ends up in unrecognized + option.option = arg[x]; + processor.next(option); + } + } + continue; + } + } + if (! option.option.isEmpty()) { + if (currentDefinition.name != option.option) { + // find the definition to match. + foreach (OptionDefinition definition, definitions) { + if (definition.name == option.option) { + currentDefinition = definition; + break; + } + } + } + if (currentDefinition.requiredParameters + currentDefinition.optionalParameters <= option.parameters.count()) + processor.next(option); + } + if (option.option.isEmpty()) + arguments << arg; + else + option.parameters << arg; + } + processor.next(option); + + if (requiredArguments > arguments.count()) + errors.append(QLatin1String("Not enough arguments, usage: ") + QString::fromLocal8Bit(argumentStrings[0]) + + QLatin1Char(' ') + argumentDefinition); + +/* + foreach (QString key, options.keys()) { + ParsedOption p = options[key]; + qDebug() << "-> " << p.option; + foreach (QString v, p.parameters) + qDebug() << " +" << v; + } + qDebug() << "---"; + foreach (QString arg, arguments) { + qDebug() << arg; + } +*/ +} + +// ----------------------------------- + + +// static +void CommandLineParser::init(int argc, char **argv) +{ + if (self) + delete self; + self = new CommandLineParser(argc, argv); +} + +// static +void CommandLineParser::addOptionDefinitions(const CommandLineOption * optionList) +{ + if (!self) { + qWarning() << "CommandLineParser:: Use init before addOptionDefinitions!"; + return; + } + self->d->addDefinitions(optionList); +} + +// static +CommandLineParser *CommandLineParser::instance() +{ + return self; +} + +// static +void CommandLineParser::setArgumentDefinition(const char *definition) +{ + if (!self) { + qWarning() << "CommandLineParser:: Use init before addOptionDefinitions!"; + return; + } + self->d->setArgumentDefinition(definition); +} + + +CommandLineParser::CommandLineParser(int argc, char **argv) + : d(new Private(argc, argv)) +{ +} + +CommandLineParser::~CommandLineParser() +{ + delete d; +} + +void CommandLineParser::usage(const QString &name, const QString &argumentDescription) +{ + QTextStream cout(stdout, QIODevice::WriteOnly); + cout << "Usage: " << d->argumentStrings[0]; + if (! name.isEmpty()) + cout << " " << name; + if (d->definitions.count()) + cout << " [OPTION]"; + if (! argumentDescription.isEmpty()) + cout << " " << argumentDescription; + cout << endl << endl; + + if (d->definitions.count() > 0) + cout << "Options:" << endl; + int commandLength = 0; + foreach (Private::OptionDefinition definition, d->definitions) + commandLength = qMax(definition.name.length(), commandLength); + + foreach (Private::OptionDefinition definition, d->definitions) { + cout << " "; + if (definition.shortName == 0) + cout << " --"; + else + cout << "-" << definition.shortName << " --"; + cout << definition.name; + for (int i = definition.name.length(); i <= commandLength; i++) + cout << ' '; + cout << definition.comment <<endl; + } +} + +QStringList CommandLineParser::options() const +{ + d->parse(); + return d->options.keys(); +} + +bool CommandLineParser::contains(const QString & key) const +{ + d->parse(); + return d->options.contains(key); +} + +QStringList CommandLineParser::arguments() const +{ + d->parse(); + return d->arguments; +} + +QStringList CommandLineParser::undefinedOptions() const +{ + d->parse(); + return d->undefinedOptions; +} + +QString CommandLineParser::optionArgument(const QString &optionName, const QString &defaultValue) const +{ + QStringList answer = optionArguments(optionName); + if (answer.isEmpty()) + return defaultValue; + return answer.first(); +} + +QStringList CommandLineParser::optionArguments(const QString &optionName) const +{ + if (! contains(optionName)) + return QStringList(); + Private::ParsedOption po = d->options[optionName]; + return po.parameters; +} + +QStringList CommandLineParser::parseErrors() const +{ + d->parse(); + return d->errors; +} diff --git a/src/CommandLineParser.h b/src/CommandLineParser.h new file mode 100644 index 0000000..62c04bb --- /dev/null +++ b/src/CommandLineParser.h @@ -0,0 +1,98 @@ +/* + * This file is part of the vng project + * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef COMMANDLINEPARSER_H +#define COMMANDLINEPARSER_H + +#include <QString> + +struct CommandLineOption { + /** + * The specification of an option includes the name of the option the user must pass and optional arguments it has. + * Example specifications are; + * <ol> + * <li>"-a, --all" </li> + * <li>"--version" </li> + * <li>"--type name" </li> + * <li>"-f, --format name [suffix] [foo]" </li> </ol> + * Number 1 allows the user to either type -a or --all (or /A on Windows) to activate this option. + * Number 2 allows the user to type --version to activate this option. + * Number 3 requires the user to type a single argument after the option. + * Number 4 Allows the user to either use -f or --format, which is followed by one required argument + * and optionally 2 more arguments. + */ + const char *specification; + /** + * A textual description of the option that will be printed when the user asks for help. + */ + const char *description; +}; + +#define CommandLineLastOption { 0, 0 } + +/** + * The CommandLineParser singleton + */ +class CommandLineParser +{ +public: + static void init(int argc, char *argv[]); + static void addOptionDefinitions(const CommandLineOption *definitions); + static void setArgumentDefinition(const char *definition); + static CommandLineParser *instance(); + + ~CommandLineParser(); + + void usage(const QString &name, const QString &argumentDescription = QString()); + + /// return the options that the user passed + QStringList options() const; + /** + * returns true if the option was found. + * Consider the following definition "--expert level" The user can type as an argument + * "--expert 10". Calling contains("expert") will return true. + * @see optionArgument() + */ + bool contains(const QString & key) const; + + /// returns the list of items that are not options, note that the first one is the name of the command called + QStringList arguments() const; + + /// return the list of options that the user passed but we don't have a definition for. + QStringList undefinedOptions() const; + + /** + * Return the argument passed to an option. + * Consider the following definition "--expert level" The user can type as an argument + * "--expert 10". Calling optionArgument("expert") will return a string "10" + * @see contains() + */ + QString optionArgument(const QString &optionName, const QString &defaultValue = QString()) const; + QStringList optionArguments(const QString &optionName) const; + + QStringList parseErrors() const; + +private: + CommandLineParser(int argc, char **argv); + class Private; + Private * const d; + static CommandLineParser *self; +}; + +#endif + diff --git a/src/main.cpp b/src/main.cpp index 35535c9..ab17cd1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,10 +18,11 @@ #include <QCoreApplication> #include <QFile> #include <QStringList> +#include <QTextStream> #include <stdio.h> -#include "options.h" +#include "CommandLineParser.h" #include "ruleparser.h" #include "repository.h" #include "svn.h" @@ -53,19 +54,56 @@ QHash<QByteArray, QByteArray> loadIdentityMapFile(const QString &fileName) return result; } +static const CommandLineOption options[] = { + {"--identity-map FILENAME", "provide map between svn username and email"}, + {"--rules FILENAME", "the rules file that determines what goes where"}, + {"--add-metadata", "if passed, each git commit will have svn commit info"}, + {"--resume-from revision", "start importing at svn revision number"}, + {"--max-rev revision", "stop importing at svn revision number"}, + //{"--dry-run", "don't actually write anything"}, TODO + {"-h, --help", "show help"}, + {"-v, --version", "show version"}, + CommandLineLastOption +}; + int main(int argc, char **argv) { - QCoreApplication app(argc, argv); - - Options options; - options.parseArguments(app.arguments()); + CommandLineParser::init(argc, argv); + CommandLineParser::addOptionDefinitions(options); + CommandLineParser *args = CommandLineParser::instance(); + if (args->contains(QLatin1String("help")) || args->arguments().count() != 1) { + args->usage(QString(), "[Path to subversion repo]"); + return 0; + } + if (args->undefinedOptions().count()) { + QTextStream out(stderr); + out << "svn-all-fast-export failed: "; + bool first = true; + foreach (QString option, args->undefinedOptions()) { + if (!first) + out << " : "; + out << "unrecognized option or missing argument for; `" << option << "'" << endl; + first = false; + } + return 10; + } + if (!args->contains("rules")) { + QTextStream out(stderr); + out << "svn-all-fast-export failed: please specify the rules using the 'rules' argument\n"; + return 11; + } + if (!args->contains("identity-map")) { + QTextStream out(stderr); + out << "WARNING; no identity-map specified, all commits will be without email address\n\n"; + } + QCoreApplication app(argc, argv); // Load the configuration - Rules rules(options.ruleFile); + Rules rules(args->optionArgument(QLatin1String("rules"))); rules.load(); - int min_rev = options.options.value("resume-from").toInt(); - int max_rev = options.options.value("max-rev").toInt(); + int min_rev = args->optionArgument(QLatin1String("resume-from")).toInt(); + int max_rev = args->optionArgument(QLatin1String("max-rev")).toInt(); if (min_rev < 1) min_rev = 1; @@ -77,10 +115,10 @@ int main(int argc, char **argv) } Svn::initialize(); - Svn svn(options.pathToRepository); + Svn svn(args->arguments().first()); svn.setMatchRules(rules.matchRules()); svn.setRepositories(repositories); - svn.setIdentityMap(loadIdentityMapFile(options.options.value("identity-map"))); + svn.setIdentityMap(loadIdentityMapFile(args->optionArgument("identity-map"))); if (max_rev < 1) max_rev = svn.youngestRevision(); diff --git a/src/options.cpp b/src/options.cpp deleted file mode 100644 index 892b6e1..0000000 --- a/src/options.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2007 Thiago Macieira <thiago@kde.org> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "options.h" - -#include <QSet> -#include <QStringList> - -#include <stdio.h> -#include <stdlib.h> - -Options* Options::globalOptions = 0; - -Options::Options() -{ - globalOptions = this; -} - -Options::~Options() -{ -} - -void Options::showHelp() -{ - printf("Usage: svn-all-fast-export configfile path-to-svn\n"); -} - -void Options::parseArguments(const QStringList &argumentList) -{ - QSet<QString> validOptions; - validOptions << "help" << "metadata"; - - QSet<QString> validOptionsWithComplement; - validOptionsWithComplement << "resume-from" << "max-rev" << "identity-map"; - - QStringList arguments = argumentList; - arguments.takeFirst(); // the first one is the executable name; drop it - while (!arguments.isEmpty()) { - QString arg = arguments.takeFirst(); - QString complement; - - if (arg == "--") - break; - - if (arg.startsWith("--")) - arg = arg.mid(1); // drop double dashes to single - - if (arg.startsWith("-no-")) { - complement = "no"; - arg = arg.mid(4); - } else if (!arg.startsWith("-")) { - // non-option arg - arguments.prepend(arg); - break; - } else { // starts with "-" - arg = arg.mid(1); - } - - if (arg.contains('=') && complement.isEmpty()) { - int pos = arg.indexOf('='); - complement = arg.mid(pos + 1); - arg.truncate(pos); - } - - if (validOptionsWithComplement.contains(arg)) { - if (arguments.isEmpty()) { - fprintf(stderr, "Option -%s requires an argument\n", qPrintable(arg)); - exit(2); - } - - if (options.contains(arg)) { - fprintf(stderr, "Option -%s given more than once\n", qPrintable(arg)); - exit(2); - } - - if (!complement.isEmpty()) - options[arg] = complement; - else if (!arguments.isEmpty()) - options[arg] = arguments.takeFirst(); - else { - fprintf(stderr, "Option -%s requires an argument\n", qPrintable(arg)); - exit(2); - } - continue; - } else if (validOptions.contains(arg)) { - if (switches.contains(arg)) { - fprintf(stderr, "Option -%s given more than once\n", qPrintable(arg)); - exit(2); - } - - switches[arg] = !(complement == "no"); - } else { - if (complement == "no") - fprintf(stderr, "Invalid option: -no-%s\n", qPrintable(arg)); - else - fprintf(stderr, "Invalid option: -%s\n", qPrintable(arg)); - exit(2); - } - } - - if (switches.value("help")) { - showHelp(); - exit(0); - } else if (arguments.count() < 2) { - showHelp(); - exit(2); - } - - ruleFile = arguments.takeFirst(); - pathToRepository = arguments.takeFirst(); -} diff --git a/src/options.h b/src/options.h deleted file mode 100644 index 30d1aab..0000000 --- a/src/options.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2007 Thiago Macieira <thiago@kde.org> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef OPTIONS_H -#define OPTIONS_H - -#include <QHash> -#include <QString> - -class Options -{ -public: - // mandatory non-option arguments - QString ruleFile; - QString pathToRepository; - - // optional extras - QHash<QString, QString> options; - QHash<QString, bool> switches; - - Options(); - ~Options(); - - void showHelp(); - void parseArguments(const QStringList &arguments); - - static Options *globalOptions; -}; - -#endif diff --git a/src/repository.cpp b/src/repository.cpp index 72d681e..c0f0122 100644 --- a/src/repository.cpp +++ b/src/repository.cpp @@ -17,7 +17,7 @@ */ #include "repository.h" -#include "options.h" +#include "CommandLineParser.h" #include <QTextStream> #include <QDebug> #include <QDir> @@ -221,7 +221,7 @@ void Repository::finalizeTags() QByteArray message = tag.log; if (!message.endsWith('\n')) message += '\n'; - if (Options::globalOptions->switches.value("metadata", true)) + if (CommandLineParser::instance()->contains("add-metadata")) message += "\nsvn path=" + tag.svnprefix + "; revision=" + QByteArray::number(tag.revnum) + "\n"; { @@ -334,7 +334,7 @@ void Repository::Transaction::commit() QByteArray message = log; if (!message.endsWith('\n')) message += '\n'; - if (Options::globalOptions->switches.value("metadata", true)) + if (CommandLineParser::instance()->contains("add-metadata")) message += "\nsvn path=" + svnprefix + "; revision=" + QByteArray::number(revnum) + "\n"; { diff --git a/src/src.pro b/src/src.pro index 96fb236..79248bf 100644 --- a/src/src.pro +++ b/src/src.pro @@ -17,5 +17,13 @@ INCLUDEPATH += . $$SVN_INCLUDE $$APR_INCLUDE LIBS += -lsvn_fs-1 -lsvn_repos-1 # Input -SOURCES += options.cpp ruleparser.cpp repository.cpp svn.cpp main.cpp -HEADERS += options.h ruleparser.h repository.h svn.h +SOURCES += ruleparser.cpp \ + repository.cpp \ + svn.cpp \ + main.cpp \ + CommandLineParser.cpp \ + +HEADERS += ruleparser.h \ + repository.h \ + svn.h \ + CommandLineParser.h \ |