| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 | // STL includes#include <stdexcept>// Qt includes #include <QHash>#include <QStringList>#include <QTextStream>#include <QDebug>#include <QSettings>#include <QPointer>// CTK includes#include "ctkCommandLineParser.h"namespace{// --------------------------------------------------------------------------class CommandLineParserArgumentDescription{public:  CommandLineParserArgumentDescription(    const QString& longArg, const QString& longArgPrefix,    const QString& shortArg, const QString& shortArgPrefix,    QVariant::Type type, const QString& argHelp,    const QVariant& defaultValue, bool ignoreRest,    bool deprecated)      : LongArg(longArg), LongArgPrefix(longArgPrefix),      ShortArg(shortArg), ShortArgPrefix(shortArgPrefix),      ArgHelp(argHelp), IgnoreRest(ignoreRest), NumberOfParametersToProcess(0),      Deprecated(deprecated), DefaultValue(defaultValue), Value(type), ValueType(type)  {    if (defaultValue.isValid())      {      Value = defaultValue;      }    switch (type)      {      case QVariant::String:        {        NumberOfParametersToProcess = 1;        RegularExpression = ".*";        }        break;      case QVariant::Bool:        {        NumberOfParametersToProcess = 0;        RegularExpression = "";        }        break;      case QVariant::StringList:        {        NumberOfParametersToProcess = -1;        RegularExpression = ".*";        }        break;      case QVariant::Int:        {        NumberOfParametersToProcess = 1;        RegularExpression = "-?[0-9]+";        ExactMatchFailedMessage = "A negative or positive integer is expected.";        }        break;      default:        ExactMatchFailedMessage = QString("Type %1 not supported.").arg(static_cast<int>(type));      }  }  ~CommandLineParserArgumentDescription(){}  bool addParameter(const QString& value);  QString helpText(int fieldWidth, const char charPad, const QString& settingsValue = "");  QString LongArg;  QString LongArgPrefix;  QString ShortArg;  QString ShortArgPrefix;  QString ArgHelp;  bool    IgnoreRest;  int     NumberOfParametersToProcess;  QString RegularExpression;  QString ExactMatchFailedMessage;  bool    Deprecated;  QVariant       DefaultValue;  QVariant       Value;  QVariant::Type ValueType;};// --------------------------------------------------------------------------bool CommandLineParserArgumentDescription::addParameter(const QString& value){  if (!RegularExpression.isEmpty())    {    // Validate value    QRegExp regexp(this->RegularExpression);    if (!regexp.exactMatch(value))      {      return false;      }    }  switch (Value.type())    {    case QVariant::String:      {      Value.setValue(value);      }      break;    case QVariant::Bool:      {      Value.setValue(!QString::compare(value, "true", Qt::CaseInsensitive));      }      break;    case QVariant::StringList:      {      if (Value.isNull())        {        QStringList list;        list << value;        Value.setValue(list);        }      else        {        QStringList list = Value.toStringList();        list << value;        Value.setValue(list);        }      }      break;    case QVariant::Int:      {      Value.setValue(value.toInt());      }      break;    default:      return false;    }  return true;}// --------------------------------------------------------------------------QString CommandLineParserArgumentDescription::helpText(int fieldWidth, const char charPad,                                                       const QString& settingsValue){  QString text;  QTextStream stream(&text);  stream.setFieldAlignment(QTextStream::AlignLeft);  stream.setPadChar(charPad);  QString shortAndLongArg;  if (!this->ShortArg.isEmpty())    {    shortAndLongArg += QString("  %1%2").arg(this->ShortArgPrefix).arg(this->ShortArg);    }  if (!this->LongArg.isEmpty())    {    if (this->ShortArg.isEmpty())      {      shortAndLongArg.append("  ");      }    else      {      shortAndLongArg.append(", ");      }    shortAndLongArg += QString("%1%2").arg(this->LongArgPrefix).arg(this->LongArg);    }  if(!this->ArgHelp.isEmpty())    {    stream.setFieldWidth(fieldWidth);    }  stream  << shortAndLongArg;  stream.setFieldWidth(0);  stream << this->ArgHelp;  if (!settingsValue.isNull())    {    stream << " (default: " << settingsValue << ")";    }  else if (!this->DefaultValue.isNull())    {    stream << " (default: " << this->DefaultValue.toString() << ")";    }  stream << "\n";  return text;}}// --------------------------------------------------------------------------// ctkCommandLineParser::ctkInternal class// --------------------------------------------------------------------------class ctkCommandLineParser::ctkInternal{public:  ctkInternal(QSettings* settings)    : Debug(false), FieldWidth(0), UseQSettings(false),      Settings(settings), MergeSettings(true), StrictMode(false)  {}  ~ctkInternal() { qDeleteAll(ArgumentDescriptionList); }    CommandLineParserArgumentDescription* argumentDescription(const QString& argument);    QList<CommandLineParserArgumentDescription*>                 ArgumentDescriptionList;  QHash<QString, CommandLineParserArgumentDescription*>        ArgNameToArgumentDescriptionMap;  QMap<QString, QList<CommandLineParserArgumentDescription*> > GroupToArgumentDescriptionListMap;    QStringList UnparsedArguments;   QStringList ProcessedArguments;  QString     ErrorString;  bool        Debug;  int         FieldWidth;  QString     LongPrefix;  QString     ShortPrefix;  QString     CurrentGroup;  bool        UseQSettings;  QPointer<QSettings> Settings;  QString     DisableQSettingsLongArg;  QString     DisableQSettingsShortArg;  bool        MergeSettings;  bool        StrictMode;};// --------------------------------------------------------------------------// ctkCommandLineParser::ctkInternal methods// --------------------------------------------------------------------------CommandLineParserArgumentDescription*  ctkCommandLineParser::ctkInternal::argumentDescription(const QString& argument){  QString unprefixedArg = argument;  if (!LongPrefix.isEmpty() && argument.startsWith(LongPrefix))    {    // Case when (ShortPrefix + UnPrefixedArgument) matches LongPrefix    if (argument == LongPrefix && !ShortPrefix.isEmpty() && argument.startsWith(ShortPrefix))      {      unprefixedArg = argument.mid(ShortPrefix.length());      }    else      {      unprefixedArg = argument.mid(LongPrefix.length());      }    }  else if (!ShortPrefix.isEmpty() && argument.startsWith(ShortPrefix))    {    unprefixedArg = argument.mid(ShortPrefix.length());    }  else if (!LongPrefix.isEmpty() && !ShortPrefix.isEmpty())    {    return 0;    }  if (this->ArgNameToArgumentDescriptionMap.contains(unprefixedArg))    {    return this->ArgNameToArgumentDescriptionMap[unprefixedArg];    }  return 0;}// --------------------------------------------------------------------------// ctkCommandLineParser methods// --------------------------------------------------------------------------ctkCommandLineParser::ctkCommandLineParser(QObject* newParent) : Superclass(newParent){  this->Internal = new ctkInternal(0);}// --------------------------------------------------------------------------ctkCommandLineParser::ctkCommandLineParser(QSettings* settings, QObject* newParent) :    Superclass(newParent){  this->Internal = new ctkInternal(settings);}// --------------------------------------------------------------------------ctkCommandLineParser::~ctkCommandLineParser(){  delete this->Internal;}// --------------------------------------------------------------------------QHash<QString, QVariant> ctkCommandLineParser::parseArguments(const QStringList& arguments,                                                              bool* ok){  // Reset  this->Internal->UnparsedArguments.clear();  this->Internal->ProcessedArguments.clear();  this->Internal->ErrorString.clear();  foreach (CommandLineParserArgumentDescription* desc,           this->Internal->ArgumentDescriptionList)    {    desc->Value = QVariant(desc->ValueType);    if (desc->DefaultValue.isValid())      {      desc->Value = desc->DefaultValue;      }    }  bool error = false;  bool ignoreRest = false;  bool useSettings = this->Internal->UseQSettings;  CommandLineParserArgumentDescription * currentArgDesc = 0;  QList<CommandLineParserArgumentDescription*> parsedArgDescriptions;  for(int i = 1; i < arguments.size(); ++i)    {    QString argument = arguments.at(i);    if (this->Internal->Debug) { qDebug() << "Processing" << argument; }    // should argument be ignored ?    if (ignoreRest)      {      if (this->Internal->Debug)        {        qDebug() << "  Skipping: IgnoreRest flag was been set";        }      this->Internal->UnparsedArguments << argument;      continue;      }    // Skip if the argument does not start with the defined prefix    if (!(argument.startsWith(this->Internal->LongPrefix)      || argument.startsWith(this->Internal->ShortPrefix)))      {      if (this->Internal->StrictMode)        {        this->Internal->ErrorString = QString("Unknown argument %1").arg(argument);        error = true;        break;        }      if (this->Internal->Debug)        {        qDebug() << "  Skipping: It does not start with the defined prefix";        }      this->Internal->UnparsedArguments << argument;      continue;      }    // Skip if argument has already been parsed ...    if (this->Internal->ProcessedArguments.contains(argument))      {      if (this->Internal->StrictMode)        {        this->Internal->ErrorString = QString("Argument %1 already processed !").arg(argument);        error = true;        break;        }      if (this->Internal->Debug)        {        qDebug() << "  Skipping: Already processed !";        }      continue;      }    // Retrieve corresponding argument description    currentArgDesc = this->Internal->argumentDescription(argument);    // Is there a corresponding argument description ?    if (currentArgDesc)      {      // If the argument is deprecated, print the help text but continue processing      if (currentArgDesc->Deprecated)        {        qWarning().nospace() << "Deprecated argument " << argument << ": "  << currentArgDesc->ArgHelp;        }      else        {        parsedArgDescriptions.push_back(currentArgDesc);        }      // Is the argument the special "disable QSettings" argument?      if ((!currentArgDesc->LongArg.isEmpty() && currentArgDesc->LongArg == this->Internal->DisableQSettingsLongArg)        || (!currentArgDesc->ShortArg.isEmpty() && currentArgDesc->ShortArg == this->Internal->DisableQSettingsShortArg))        {        useSettings = false;        }      this->Internal->ProcessedArguments << currentArgDesc->ShortArg << currentArgDesc->LongArg;      int numberOfParametersToProcess = currentArgDesc->NumberOfParametersToProcess;      ignoreRest = currentArgDesc->IgnoreRest;      if (this->Internal->Debug && ignoreRest)        {        qDebug() << "  IgnoreRest flag is True";        }      // Is the number of parameters associated with the argument being processed known ?      if (numberOfParametersToProcess == 0)        {        currentArgDesc->addParameter("true");        }      else if (numberOfParametersToProcess > 0)        {        QString missingParameterError =            "Argument %1 has %2 value(s) associated whereas exacly %3 are expected.";        for(int j=1; j <= numberOfParametersToProcess; ++j)          {          if (i + j >= arguments.size())            {            this->Internal->ErrorString =                 missingParameterError.arg(argument).arg(j-1).arg(numberOfParametersToProcess);            if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }            if (ok) { *ok = false; }            return QHash<QString, QVariant>();            }          QString parameter = arguments.at(i + j);          if (this->Internal->Debug)            {            qDebug() << "  Processing parameter" << j << ", value:" << parameter;            }          if (this->argumentAdded(parameter))            {            this->Internal->ErrorString =                missingParameterError.arg(argument).arg(j-1).arg(numberOfParametersToProcess);            if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }            if (ok) { *ok = false; }            return QHash<QString, QVariant>();            }          if (!currentArgDesc->addParameter(parameter))            {            this->Internal->ErrorString = QString(                "Value(s) associated with argument %1 are incorrect. %2").                arg(argument).arg(currentArgDesc->ExactMatchFailedMessage);            if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }            if (ok) { *ok = false; }            return QHash<QString, QVariant>();            }          }        // Update main loop increment        i = i + numberOfParametersToProcess;        }      else if (numberOfParametersToProcess == -1)        {        if (this->Internal->Debug)          {          qDebug() << "  Proccessing StringList ...";          }        int j = 1;        while(j + i < arguments.size())          {          if (this->argumentAdded(arguments.at(j + i)))            {            if (this->Internal->Debug)              {              qDebug() << "  No more parameter for" << argument;              }            break;            }          QString parameter = arguments.at(j + i);          if (this->Internal->Debug)            {            qDebug() << "  Processing parameter" << j << ", value:" << parameter;            }          if (!currentArgDesc->addParameter(parameter))            {            this->Internal->ErrorString = QString(                "Value(s) associated with argument %1 are incorrect. %2").                arg(argument).arg(currentArgDesc->ExactMatchFailedMessage);            if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }            if (ok) { *ok = false; }            return QHash<QString, QVariant>();            }          j++;          }        // Update main loop increment        i = i + j;        }      }    else      {      if (this->Internal->StrictMode)        {        this->Internal->ErrorString = QString("Unknown argument %1").arg(argument);        error = true;        break;        }      if (this->Internal->Debug)        {        qDebug() << "  Skipping: Unknown argument";        }      this->Internal->UnparsedArguments << argument;      }    }  if (ok)    {    *ok = !error;    }  QSettings* settings = 0;  if (this->Internal->UseQSettings && useSettings)    {    if (this->Internal->Settings)      {      settings = this->Internal->Settings;      }    else      {      // Use a default constructed QSettings instance      settings = new QSettings();      }    }  QHash<QString, QVariant> parsedArguments;  QListIterator<CommandLineParserArgumentDescription*> it(this->Internal->ArgumentDescriptionList);  while (it.hasNext())    {    QString key;    CommandLineParserArgumentDescription* desc = it.next();    if (!desc->LongArg.isEmpty())      {      key = desc->LongArg;      }    else      {      key = desc->ShortArg;      }    if (parsedArgDescriptions.contains(desc))      {      // The argument was supplied on the command line, so use the given value      if (this->Internal->MergeSettings && settings)        {        // Merge with QSettings        QVariant settingsVal = settings->value(key);        if (desc->ValueType == QVariant::StringList &&            settingsVal.canConvert(QVariant::StringList))          {          QStringList stringList = desc->Value.toStringList();          stringList.append(settingsVal.toStringList());          parsedArguments.insert(key, stringList);          }        else          {          // do a normal insert          parsedArguments.insert(key, desc->Value);          }        }      else        {        // No merging, just insert all user values        parsedArguments.insert(key, desc->Value);        }      }    else      {      if (settings)        {        // If there is a valid QSettings entry for the argument, use the value        QVariant settingsVal = settings->value(key, desc->Value);        if (!settingsVal.isNull())          {          parsedArguments.insert(key, settingsVal);          }        }      else        {        // Just insert the arguments with valid default values        if (!desc->Value.isNull())          {          parsedArguments.insert(key, desc->Value);          }        }      }    }  // If we created a default QSettings instance, delete it  if (settings && !this->Internal->Settings)    {    delete settings;    }  return parsedArguments;}// -------------------------------------------------------------------------QHash<QString, QVariant> ctkCommandLineParser::parseArguments(int argc, char** argv, bool* ok){  QStringList arguments;  // Create a QStringList of arguments  for(int i = 0; i < argc; ++i)    {    arguments << argv[i];    }  return this->parseArguments(arguments, ok);}// -------------------------------------------------------------------------QString ctkCommandLineParser::errorString() const{  return this->Internal->ErrorString;}// -------------------------------------------------------------------------const QStringList& ctkCommandLineParser::unparsedArguments() const{  return this->Internal->UnparsedArguments;}// --------------------------------------------------------------------------void ctkCommandLineParser::addArgument(const QString& longarg, const QString& shortarg,                                       QVariant::Type type, const QString& argHelp,                                       const QVariant& defaultValue, bool ignoreRest,                                       bool deprecated){  Q_ASSERT_X(!(longarg.isEmpty() && shortarg.isEmpty()), "addArgument",             "both long and short argument names are empty");  if (longarg.isEmpty() && shortarg.isEmpty()) { return; }  Q_ASSERT_X(!defaultValue.isValid() || defaultValue.type() == type, "addArgument",             "defaultValue type does not match");  if (defaultValue.isValid() && defaultValue.type() != type)    throw std::logic_error("The QVariant type of defaultValue does not match the specified type");  /* Make sure it's not already added */  bool added = this->Internal->ArgNameToArgumentDescriptionMap.contains(longarg);  Q_ASSERT_X(!added, "addArgument", "long argument already added");  if (added) { return; }  added = this->Internal->ArgNameToArgumentDescriptionMap.contains(shortarg);  Q_ASSERT_X(!added, "addArgument", "short argument already added");  if (added) { return; }  CommandLineParserArgumentDescription* argDesc =    new CommandLineParserArgumentDescription(longarg, this->Internal->LongPrefix,                                             shortarg, this->Internal->ShortPrefix, type,                                             argHelp, defaultValue, ignoreRest, deprecated);  int argWidth = 0;  if (!longarg.isEmpty())    {    this->Internal->ArgNameToArgumentDescriptionMap[longarg] = argDesc;    argWidth += longarg.length() + this->Internal->LongPrefix.length();    }  if (!shortarg.isEmpty())    {    this->Internal->ArgNameToArgumentDescriptionMap[shortarg] = argDesc;    argWidth += shortarg.length() + this->Internal->ShortPrefix.length() + 2;    }  argWidth += 5;  // Set the field width for the arguments  if (argWidth > this->Internal->FieldWidth)    {    this->Internal->FieldWidth = argWidth;    }  this->Internal->ArgumentDescriptionList << argDesc;  this->Internal->GroupToArgumentDescriptionListMap[this->Internal->CurrentGroup] << argDesc;}// --------------------------------------------------------------------------void ctkCommandLineParser::addDeprecatedArgument(    const QString& longarg, const QString& shortarg, const QString& argHelp){  addArgument(longarg, shortarg, QVariant::StringList, argHelp, QVariant(), false, true);}// --------------------------------------------------------------------------bool ctkCommandLineParser::setExactMatchRegularExpression(    const QString& argument, const QString& expression, const QString& exactMatchFailedMessage){  CommandLineParserArgumentDescription * argDesc =      this->Internal->argumentDescription(argument);  if (!argDesc)    {    return false;    }  if (argDesc->Value.type() == QVariant::Bool)    {    return false;    }  argDesc->RegularExpression = expression;  argDesc->ExactMatchFailedMessage = exactMatchFailedMessage;  return true;}// --------------------------------------------------------------------------int ctkCommandLineParser::fieldWidth() const{  return this->Internal->FieldWidth;}// --------------------------------------------------------------------------void ctkCommandLineParser::beginGroup(const QString& description){  this->Internal->CurrentGroup = description;}// --------------------------------------------------------------------------void ctkCommandLineParser::endGroup(){  this->Internal->CurrentGroup.clear();}// --------------------------------------------------------------------------void ctkCommandLineParser::enableSettings(const QString& disableLongArg, const QString& disableShortArg){  this->Internal->UseQSettings = true;  this->Internal->DisableQSettingsLongArg = disableLongArg;  this->Internal->DisableQSettingsShortArg = disableShortArg;}// --------------------------------------------------------------------------void ctkCommandLineParser::mergeSettings(bool merge){  this->Internal->MergeSettings = merge;}// --------------------------------------------------------------------------bool ctkCommandLineParser::settingsEnabled() const{  return this->Internal->UseQSettings;}// --------------------------------------------------------------------------QString ctkCommandLineParser::helpText(const char charPad) const{  QString text;  QTextStream stream(&text);  QList<CommandLineParserArgumentDescription*> deprecatedArgs;  // Loop over grouped argument descriptions  QMapIterator<QString, QList<CommandLineParserArgumentDescription*> > it(      this->Internal->GroupToArgumentDescriptionListMap);  while(it.hasNext())    {    it.next();    if (!it.key().isEmpty())      {      stream << "\n" << it.key() << "\n";      }    foreach(CommandLineParserArgumentDescription* argDesc, it.value())      {      if (argDesc->Deprecated)        {        deprecatedArgs << argDesc;        }      else        {        // Extract associated value from settings if any        QString settingsValue;        if (this->Internal->Settings)          {          QString key;          if (!argDesc->LongArg.isEmpty())            {            key = argDesc->LongArg;            }          else            {            key = argDesc->ShortArg;            }          settingsValue = this->Internal->Settings->value(key).toString();          }        stream << argDesc->helpText(this->Internal->FieldWidth, charPad, settingsValue);        }      }    }  if (!deprecatedArgs.empty())    {    stream << "\nDeprecated arguments:\n";    foreach(CommandLineParserArgumentDescription* argDesc, deprecatedArgs)      {      stream << argDesc->helpText(this->Internal->FieldWidth, charPad);      }    }  return text;}// --------------------------------------------------------------------------bool ctkCommandLineParser::argumentAdded(const QString& argument) const{  return this->Internal->ArgNameToArgumentDescriptionMap.contains(argument);}// --------------------------------------------------------------------------bool ctkCommandLineParser::argumentParsed(const QString& argument) const{  return this->Internal->ProcessedArguments.contains(argument);}// --------------------------------------------------------------------------void ctkCommandLineParser::setArgumentPrefix(const QString& longPrefix, const QString& shortPrefix){  this->Internal->LongPrefix = longPrefix;  this->Internal->ShortPrefix = shortPrefix;}// --------------------------------------------------------------------------void ctkCommandLineParser::setStrictModeEnabled(bool strictMode){  this->Internal->StrictMode = strictMode;}
 |