ctkCommandLineParser.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. // Qt includes
  2. #include <QHash>
  3. #include <QStringList>
  4. #include <QTextStream>
  5. #include <QDebug>
  6. // CTK includes
  7. #include "ctkCommandLineParser.h"
  8. namespace
  9. {
  10. class CommandLineParserArgumentDescription
  11. {
  12. public:
  13. CommandLineParserArgumentDescription(
  14. const QString& longArg, const QString& shortArg, QVariant::Type type,
  15. const QString& argHelp, const QVariant& defaultValue, bool ignoreRest)
  16. : LongArg(longArg), ShortArg(shortArg), ArgHelp(argHelp),
  17. IgnoreRest(ignoreRest), NumberOfParametersToProcess(0),
  18. Value(type)
  19. {
  20. if (defaultValue.isValid())
  21. {
  22. Value = defaultValue;
  23. }
  24. switch (type)
  25. {
  26. case QVariant::String:
  27. {
  28. NumberOfParametersToProcess = 1;
  29. RegularExpression = ".*";
  30. }
  31. break;
  32. case QVariant::Bool:
  33. {
  34. NumberOfParametersToProcess = 0;
  35. RegularExpression = "";
  36. }
  37. break;
  38. case QVariant::StringList:
  39. {
  40. NumberOfParametersToProcess = -1;
  41. RegularExpression = ".*";
  42. }
  43. break;
  44. case QVariant::Int:
  45. {
  46. NumberOfParametersToProcess = 1;
  47. RegularExpression = "-?[0-9]+";
  48. ExactMatchFailedMessage = "A negative or positive integer is expected.";
  49. }
  50. break;
  51. default:
  52. ExactMatchFailedMessage = QString("Type %1 not supported.").arg(static_cast<int>(type));
  53. }
  54. }
  55. ~CommandLineParserArgumentDescription(){}
  56. bool addParameter(const QString& value);
  57. QString helpText(int fieldWidth, const char charPad);
  58. QString LongArg;
  59. QString ShortArg;
  60. QString ArgHelp;
  61. bool IgnoreRest;
  62. int NumberOfParametersToProcess;
  63. QString RegularExpression;
  64. QString ExactMatchFailedMessage;
  65. QVariant Value;
  66. };
  67. // --------------------------------------------------------------------------
  68. bool CommandLineParserArgumentDescription::addParameter(const QString& value)
  69. {
  70. if (!RegularExpression.isEmpty())
  71. {
  72. // Validate value
  73. QRegExp regexp(this->RegularExpression);
  74. if (!regexp.exactMatch(value))
  75. {
  76. return false;
  77. }
  78. }
  79. switch (Value.type())
  80. {
  81. case QVariant::String:
  82. {
  83. Value.setValue(value);
  84. }
  85. break;
  86. case QVariant::Bool:
  87. {
  88. Value.setValue(!QString::compare(value, "true", Qt::CaseInsensitive));
  89. }
  90. break;
  91. case QVariant::StringList:
  92. {
  93. if (Value.isNull())
  94. {
  95. QStringList list;
  96. list << value;
  97. Value.setValue(list);
  98. }
  99. else
  100. {
  101. QStringList list = Value.toStringList();
  102. list << value;
  103. Value.setValue(list);
  104. }
  105. }
  106. break;
  107. case QVariant::Int:
  108. {
  109. Value.setValue(value.toInt());
  110. }
  111. break;
  112. default:
  113. return false;
  114. }
  115. return true;
  116. }
  117. // --------------------------------------------------------------------------
  118. QString CommandLineParserArgumentDescription::helpText(int fieldWidth, const char charPad)
  119. {
  120. QString text;
  121. QTextStream stream(&text);
  122. stream.setFieldAlignment(QTextStream::AlignLeft);
  123. stream.setPadChar(charPad);
  124. if (this->LongArg.isEmpty() && !this->ArgHelp.isEmpty())
  125. {
  126. stream.setFieldWidth(fieldWidth);
  127. }
  128. if (!this->ShortArg.isEmpty())
  129. {
  130. stream << QString(" %1").arg(this->ShortArg);
  131. if(!this->LongArg.isEmpty())
  132. {
  133. stream << "\n";
  134. }
  135. }
  136. if (!this->LongArg.isEmpty())
  137. {
  138. if(!this->ArgHelp.isEmpty())
  139. {
  140. stream.setFieldWidth(fieldWidth);
  141. }
  142. stream << QString(" %1").arg(this->LongArg);
  143. }
  144. stream.setFieldWidth(0);
  145. stream << this->ArgHelp << "\n";
  146. return text;
  147. }
  148. }
  149. // --------------------------------------------------------------------------
  150. // ctkCommandLineParser::ctkInternal class
  151. // --------------------------------------------------------------------------
  152. class ctkCommandLineParser::ctkInternal
  153. {
  154. public:
  155. ctkInternal():Debug(false),FieldWidth(0){}
  156. ~ctkInternal() { qDeleteAll(ArgumentDescriptionList); }
  157. CommandLineParserArgumentDescription* argumentDescription(const QString& argument);
  158. QList<CommandLineParserArgumentDescription*> ArgumentDescriptionList;
  159. QHash<QString, CommandLineParserArgumentDescription*> ArgNameToArgumentDescriptionMap;
  160. QStringList UnparsedArguments;
  161. QStringList ProcessedArguments;
  162. QString ErrorString;
  163. bool Debug;
  164. int FieldWidth;
  165. };
  166. // --------------------------------------------------------------------------
  167. // ctkCommandLineParser::ctkInternal methods
  168. // --------------------------------------------------------------------------
  169. CommandLineParserArgumentDescription*
  170. ctkCommandLineParser::ctkInternal::argumentDescription(const QString& argument)
  171. {
  172. if (this->ArgNameToArgumentDescriptionMap.contains(argument))
  173. {
  174. return this->ArgNameToArgumentDescriptionMap[argument];
  175. }
  176. return 0;
  177. }
  178. // --------------------------------------------------------------------------
  179. // ctkCommandLineParser methods
  180. // --------------------------------------------------------------------------
  181. ctkCommandLineParser::ctkCommandLineParser()
  182. {
  183. this->Internal = new ctkInternal();
  184. }
  185. // --------------------------------------------------------------------------
  186. ctkCommandLineParser::~ctkCommandLineParser()
  187. {
  188. delete this->Internal;
  189. }
  190. // --------------------------------------------------------------------------
  191. QHash<QString, QVariant> ctkCommandLineParser::parseArguments(const QStringList& arguments,
  192. bool* ok)
  193. {
  194. // Reset
  195. this->Internal->UnparsedArguments.clear();
  196. this->Internal->ProcessedArguments.clear();
  197. this->Internal->ErrorString.clear();
  198. bool ignoreRest = false;
  199. CommandLineParserArgumentDescription * currentArgDesc = 0;
  200. for(int i = 1; i < arguments.size(); ++i)
  201. {
  202. QString argument = arguments.at(i);
  203. if (this->Internal->Debug) { qDebug() << "Processing" << argument; }
  204. // should argument be ignored ?
  205. if (ignoreRest)
  206. {
  207. this->Internal->UnparsedArguments << argument;
  208. continue;
  209. }
  210. // Skip if argument has already been parsed ...
  211. if (this->Internal->ProcessedArguments.contains(argument))
  212. {
  213. qDebug() << "Skipping argument" << argument << " - Already processed !";
  214. continue;
  215. }
  216. // Retrieve corresponding argument description
  217. currentArgDesc = this->Internal->argumentDescription(argument);
  218. // Is there a corresponding argument description ?
  219. if (currentArgDesc)
  220. {
  221. this->Internal->ProcessedArguments << currentArgDesc->ShortArg << currentArgDesc->LongArg;
  222. int numberOfParametersToProcess = currentArgDesc->NumberOfParametersToProcess;
  223. ignoreRest = currentArgDesc->IgnoreRest;
  224. // Is the number of parameters associated with the argument being processed known ?
  225. if (numberOfParametersToProcess == 0)
  226. {
  227. currentArgDesc->addParameter("true");
  228. }
  229. else if (numberOfParametersToProcess > 0)
  230. {
  231. QString missingParameterError =
  232. "Argument %1 has %2 value(s) associated whereas exacly %3 are expected.";
  233. for(int j=1; j <= numberOfParametersToProcess; ++j)
  234. {
  235. if (i + j >= arguments.size())
  236. {
  237. this->Internal->ErrorString =
  238. missingParameterError.arg(argument).arg(j-1).arg(numberOfParametersToProcess);
  239. if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }
  240. if (ok) *ok = false;
  241. return QHash<QString, QVariant>();
  242. }
  243. QString parameter = arguments.at(i + j);
  244. if (this->Internal->Debug)
  245. {
  246. qDebug() << "Processing parameter" << j << ", value:" << parameter;
  247. }
  248. if (this->argumentAdded(parameter))
  249. {
  250. this->Internal->ErrorString =
  251. missingParameterError.arg(argument).arg(j-1).arg(numberOfParametersToProcess);
  252. if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }
  253. if (ok) *ok = false;
  254. return QHash<QString, QVariant>();
  255. }
  256. if (!currentArgDesc->addParameter(parameter))
  257. {
  258. this->Internal->ErrorString = QString(
  259. "Value(s) associated with argument %1 are incorrect. %2").
  260. arg(argument).arg(currentArgDesc->ExactMatchFailedMessage);
  261. if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }
  262. if (ok) *ok = false;
  263. return QHash<QString, QVariant>();
  264. }
  265. }
  266. // Update main loop increment
  267. i = i + numberOfParametersToProcess;
  268. }
  269. else if (numberOfParametersToProcess == -1)
  270. {
  271. if (this->Internal->Debug)
  272. {
  273. qDebug() << "Proccessing StringList ...";
  274. }
  275. int j = 1;
  276. while(j + i < arguments.size())
  277. {
  278. if (this->argumentAdded(arguments.at(j + i)))
  279. {
  280. if (this->Internal->Debug)
  281. {
  282. qDebug() << "No more parameter for" << argument;
  283. }
  284. break;
  285. }
  286. QString parameter = arguments.at(j + i);
  287. if (this->Internal->Debug)
  288. {
  289. qDebug() << "Processing parameter" << j << ", value:" << parameter;
  290. }
  291. if (!currentArgDesc->addParameter(parameter))
  292. {
  293. this->Internal->ErrorString = QString(
  294. "Value(s) associated with argument %1 are incorrect. %2").
  295. arg(argument).arg(currentArgDesc->ExactMatchFailedMessage);
  296. if (this->Internal->Debug) { qDebug() << this->Internal->ErrorString; }
  297. if (ok) *ok = false;
  298. return QHash<QString, QVariant>();
  299. }
  300. j++;
  301. }
  302. // Update main loop increment
  303. i = i + j;
  304. }
  305. }
  306. else
  307. {
  308. this->Internal->UnparsedArguments << argument;
  309. }
  310. }
  311. if (ok) *ok = true;
  312. QHash<QString, QVariant> parsedArguments;
  313. QListIterator<CommandLineParserArgumentDescription*> it(this->Internal->ArgumentDescriptionList);
  314. while (it.hasNext())
  315. {
  316. QString key;
  317. CommandLineParserArgumentDescription* desc = it.next();
  318. if (!desc->LongArg.isEmpty())
  319. {
  320. key = desc->LongArg;
  321. }
  322. else
  323. {
  324. key = desc->ShortArg;
  325. }
  326. parsedArguments.insert(key, desc->Value);
  327. }
  328. return parsedArguments;
  329. }
  330. // -------------------------------------------------------------------------
  331. QString ctkCommandLineParser::errorString()
  332. {
  333. return this->Internal->ErrorString;
  334. }
  335. // -------------------------------------------------------------------------
  336. const QStringList& ctkCommandLineParser::unparsedArguments()
  337. {
  338. return this->Internal->UnparsedArguments;
  339. }
  340. // --------------------------------------------------------------------------
  341. void ctkCommandLineParser::addArgument(const QString& longarg, const QString& shortarg,
  342. QVariant::Type type, const QString& argHelp,
  343. const QVariant& defaultValue, bool ignoreRest)
  344. {
  345. Q_ASSERT(!defaultValue.isValid() || defaultValue.type() == type);
  346. /* Make sure it's not already added */
  347. bool added = this->Internal->ArgNameToArgumentDescriptionMap.contains(longarg);
  348. Q_ASSERT(!added);
  349. if (added) { return; }
  350. added = this->Internal->ArgNameToArgumentDescriptionMap.contains(shortarg);
  351. Q_ASSERT(!added);
  352. if (added) { return; }
  353. CommandLineParserArgumentDescription* argDesc =
  354. new CommandLineParserArgumentDescription(longarg, shortarg, type,
  355. argHelp, defaultValue, ignoreRest);
  356. Q_ASSERT(!(longarg.isEmpty() && shortarg.isEmpty()));
  357. if (longarg.isEmpty() && shortarg.isEmpty()) { return; }
  358. if (!longarg.isEmpty())
  359. {
  360. this->Internal->ArgNameToArgumentDescriptionMap[longarg] = argDesc;
  361. int argWidth = longarg.length() + 7;
  362. if (argWidth > this->Internal->FieldWidth)
  363. {
  364. this->Internal->FieldWidth = argWidth;
  365. }
  366. }
  367. if (!shortarg.isEmpty())
  368. {
  369. this->Internal->ArgNameToArgumentDescriptionMap[shortarg] = argDesc;
  370. int argWidth = shortarg.length() + 7;
  371. if (argWidth > this->Internal->FieldWidth)
  372. {
  373. this->Internal->FieldWidth = argWidth;
  374. }
  375. }
  376. this->Internal->ArgumentDescriptionList << argDesc;
  377. }
  378. // --------------------------------------------------------------------------
  379. bool ctkCommandLineParser::setExactMatchRegularExpression(
  380. const QString& argument, const QString& expression, const QString& exactMatchFailedMessage)
  381. {
  382. CommandLineParserArgumentDescription * argDesc =
  383. this->Internal->argumentDescription(argument);
  384. if (!argDesc)
  385. {
  386. return false;
  387. }
  388. if (argDesc->Value.type() == QVariant::Bool)
  389. {
  390. return false;
  391. }
  392. argDesc->RegularExpression = expression;
  393. argDesc->ExactMatchFailedMessage = exactMatchFailedMessage;
  394. return true;
  395. }
  396. // --------------------------------------------------------------------------
  397. QString ctkCommandLineParser::helpText(const char charPad)
  398. {
  399. QString text;
  400. QTextStream stream(&text);
  401. // Loop over argument descriptions
  402. foreach(CommandLineParserArgumentDescription* argDesc,
  403. this->Internal->ArgumentDescriptionList)
  404. {
  405. stream << argDesc->helpText(this->Internal->FieldWidth, charPad);
  406. }
  407. return text;
  408. }
  409. // --------------------------------------------------------------------------
  410. bool ctkCommandLineParser::argumentAdded(const QString& argument)
  411. {
  412. return this->Internal->ArgNameToArgumentDescriptionMap.contains(argument);
  413. }
  414. // --------------------------------------------------------------------------
  415. bool ctkCommandLineParser::argumentParsed(const QString& argument)
  416. {
  417. return this->Internal->ProcessedArguments.contains(argument);
  418. }