File indexing completed on 2024-05-12 05:26:20
0001 /* 0002 * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0018 */ 0019 0020 #include "syntaxtree.h" 0021 0022 #include <QCoreApplication> 0023 #include <QDebug> 0024 0025 SyntaxTree *SyntaxTree::s_module = 0; 0026 0027 Syntax::Syntax() 0028 { 0029 } 0030 0031 Syntax::Syntax(const QString &k, const QString &helpText, std::function<bool(const QStringList &, State &)> l, Interactivity inter) 0032 : keyword(k), help(helpText), interactivity(inter), lambda(l) 0033 { 0034 } 0035 0036 void Syntax::addPositionalArgument(const Argument &argument) 0037 { 0038 arguments.push_back(argument); 0039 } 0040 0041 void Syntax::addParameter(const QString &name, const ParameterOptions &options) 0042 { 0043 parameters.insert(name, options); 0044 } 0045 0046 void Syntax::addFlag(const QString &name, const QString &help) 0047 { 0048 flags.insert(name, help); 0049 } 0050 0051 QString Syntax::usage() const 0052 { 0053 // TODO: refactor into meaningful functions? 0054 bool hasArguments = !arguments.isEmpty(); 0055 bool hasFlags = !flags.isEmpty(); 0056 bool hasOptions = !parameters.isEmpty(); 0057 bool hasSubcommand = !children.isEmpty(); 0058 0059 QString argumentsSummary; 0060 0061 QString argumentsUsage; 0062 if (hasArguments) { 0063 argumentsUsage += "\nARGUMENTS:\n"; 0064 for (const auto &arg : arguments) { 0065 if (arg.required) { 0066 argumentsSummary += QString(" <%1>").arg(arg.name); 0067 argumentsUsage += QString(" <%1>: %2\n").arg(arg.name).arg(arg.help); 0068 } else { 0069 argumentsSummary += QString(" [%1]").arg(arg.name); 0070 argumentsUsage += QString(" [%1]: %2\n").arg(arg.name).arg(arg.help); 0071 } 0072 if (arg.variadic) { 0073 argumentsSummary += "..."; 0074 } 0075 } 0076 } 0077 0078 if (hasFlags) { 0079 argumentsSummary += " [FLAGS]"; 0080 } 0081 0082 if (hasOptions) { 0083 argumentsSummary += " [OPTIONS]"; 0084 } 0085 0086 if (hasSubcommand) { 0087 if (hasArguments || hasFlags || hasOptions) { 0088 argumentsSummary = QString(" [ <SUB-COMMAND> |%1 ]").arg(argumentsSummary); 0089 } else { 0090 argumentsSummary = " <SUB-COMMAND>"; 0091 } 0092 } 0093 0094 argumentsSummary += '\n'; 0095 0096 QString subcommandsUsage; 0097 if (hasSubcommand) { 0098 subcommandsUsage += "\nSUB-COMMANDS:\n" 0099 " Use the 'help' command to find out more about a sub-command.\n\n"; 0100 for (const auto &command : children) { 0101 subcommandsUsage += QString(" %1: %2\n").arg(command.keyword).arg(command.help); 0102 } 0103 } 0104 0105 QString flagsUsage; 0106 if (hasFlags) { 0107 flagsUsage += "\nFLAGS:\n"; 0108 for (auto it = flags.constBegin(); it != flags.constEnd(); ++it) { 0109 flagsUsage += QString(" [--%1]: %2\n").arg(it.key()).arg(it.value()); 0110 } 0111 } 0112 0113 QString optionsUsage; 0114 if (hasOptions) { 0115 optionsUsage += "\nOPTIONS:\n"; 0116 for (auto it = parameters.constBegin(); it != parameters.constEnd(); ++it) { 0117 optionsUsage += " "; 0118 if (!it.value().required) { 0119 optionsUsage += QString("[--%1 $%2]").arg(it.key()).arg(it.value().name); 0120 } else { 0121 optionsUsage += QString("<--%1 $%2>").arg(it.key()).arg(it.value().name); 0122 } 0123 0124 optionsUsage += ": " + it.value().help + '\n'; 0125 } 0126 } 0127 0128 // TODO: instead of just the keyword, we might want to have the whole 0129 // command (e.g. if this is a sub-command) 0130 return QString("USAGE:\n ") + keyword + argumentsSummary + subcommandsUsage + 0131 argumentsUsage + flagsUsage + optionsUsage; 0132 } 0133 0134 SyntaxTree::SyntaxTree() 0135 { 0136 } 0137 0138 int SyntaxTree::registerSyntax(std::function<Syntax::List()> f) 0139 { 0140 m_syntax += f(); 0141 return m_syntax.size(); 0142 } 0143 0144 SyntaxTree *SyntaxTree::self() 0145 { 0146 if (!s_module) { 0147 s_module = new SyntaxTree; 0148 } 0149 0150 return s_module; 0151 } 0152 0153 Syntax::List SyntaxTree::syntax() const 0154 { 0155 return m_syntax; 0156 } 0157 0158 int SyntaxTree::run(const QStringList &commands) 0159 { 0160 int returnCode = 0; 0161 m_timeElapsed.start(); 0162 Command command = match(commands); 0163 if (command.first) { 0164 if (command.first->lambda) { 0165 bool success = command.first->lambda(command.second, m_state); 0166 if (success && command.first->interactivity == Syntax::EventDriven) { 0167 returnCode = m_state.commandStarted(); 0168 } 0169 if (!success && command.first->interactivity != Syntax::EventDriven) { 0170 returnCode = 1; 0171 } 0172 } else if (command.first->children.isEmpty()) { 0173 m_state.printError(QObject::tr("Broken command... sorry :("), "st_broken"); 0174 } else { 0175 QStringList keywordList; 0176 for (auto syntax : command.first->children) { 0177 keywordList << syntax.keyword; 0178 } 0179 const QString keywords = keywordList.join(" "); 0180 m_state.printError(QObject::tr("Command requires additional arguments, one of: %1").arg(keywords)); 0181 } 0182 } else { 0183 m_state.printError(QObject::tr("Unknown command"), "st_unknown"); 0184 } 0185 0186 if (m_state.commandTiming()) { 0187 m_state.printLine(QObject::tr("Time elapsed: %1").arg(m_timeElapsed.elapsed())); 0188 } 0189 return returnCode; 0190 } 0191 0192 SyntaxTree::Command SyntaxTree::match(const QStringList &commandLine) const 0193 { 0194 if (commandLine.isEmpty()) { 0195 return Command(); 0196 } 0197 0198 QStringListIterator commandLineIt(commandLine); 0199 0200 QVectorIterator<Syntax> syntaxIt(m_syntax); 0201 const Syntax *lastFullSyntax = 0; 0202 QStringList tailCommands; 0203 while (commandLineIt.hasNext() && syntaxIt.hasNext()) { 0204 const QString word = commandLineIt.next(); 0205 bool match = false; 0206 while (syntaxIt.hasNext()) { 0207 const Syntax &syntax = syntaxIt.next(); 0208 if (word == syntax.keyword) { 0209 lastFullSyntax = &syntax; 0210 syntaxIt = syntax.children; 0211 match = true; 0212 break; 0213 } 0214 } 0215 if (!match) { 0216 //Otherwise we would miss the just evaluated command from the tailCommands 0217 if (commandLineIt.hasPrevious()) { 0218 commandLineIt.previous(); 0219 } 0220 } 0221 } 0222 0223 if (lastFullSyntax) { 0224 while (commandLineIt.hasNext()) { 0225 tailCommands << commandLineIt.next(); 0226 } 0227 0228 return std::make_pair(lastFullSyntax, tailCommands); 0229 } 0230 0231 return Command(); 0232 } 0233 0234 Syntax::List SyntaxTree::nearestSyntax(const QStringList &words, const QString &fragment) const 0235 { 0236 Syntax::List matches; 0237 0238 // qDebug() << "words are" << words; 0239 if (words.isEmpty()) { 0240 for (const Syntax &syntax : m_syntax) { 0241 if (syntax.keyword.startsWith(fragment)) { 0242 matches.push_back(syntax); 0243 } 0244 } 0245 } else { 0246 QStringListIterator wordIt(words); 0247 QVectorIterator<Syntax> syntaxIt(m_syntax); 0248 Syntax lastFullSyntax; 0249 0250 while (wordIt.hasNext()) { 0251 const QString &word = wordIt.next(); 0252 while (syntaxIt.hasNext()) { 0253 const Syntax &syntax = syntaxIt.next(); 0254 if (word == syntax.keyword) { 0255 lastFullSyntax = syntax; 0256 syntaxIt = syntax.children; 0257 break; 0258 } 0259 } 0260 } 0261 0262 // qDebug() << "exiting with" << lastFullSyntax.keyword << words.last(); 0263 if (lastFullSyntax.keyword == words.last()) { 0264 syntaxIt = lastFullSyntax.children; 0265 while (syntaxIt.hasNext()) { 0266 Syntax syntax = syntaxIt.next(); 0267 if (fragment.isEmpty() || syntax.keyword.startsWith(fragment)) { 0268 matches.push_back(syntax); 0269 } 0270 } 0271 } 0272 } 0273 0274 return matches; 0275 } 0276 0277 State &SyntaxTree::state() 0278 { 0279 return m_state; 0280 } 0281 0282 QStringList SyntaxTree::tokenize(const QString &text) 0283 { 0284 // TODO: properly tokenize (e.g. "foo bar" should not become ['"foo', 'bar"']a 0285 static const QVector<QChar> quoters = QVector<QChar>() << '"' << '\''; 0286 QStringList tokens; 0287 QString acc; 0288 QChar closer; 0289 for (int i = 0; i < text.size(); ++i) { 0290 const QChar c = text.at(i); 0291 if (c == '\\') { 0292 ++i; 0293 if (i < text.size()) { 0294 acc.append(text.at(i)); 0295 } 0296 } else if (!closer.isNull()) { 0297 if (c == closer) { 0298 acc = acc.trimmed(); 0299 if (!acc.isEmpty()) { 0300 tokens << acc; 0301 } 0302 acc.clear(); 0303 closer = QChar(); 0304 } else { 0305 acc.append(c); 0306 } 0307 } else if (c.isSpace()) { 0308 acc = acc.trimmed(); 0309 if (!acc.isEmpty()) { 0310 tokens << acc; 0311 } 0312 acc.clear(); 0313 } else if (quoters.contains(c)) { 0314 closer = c; 0315 } else { 0316 acc.append(c); 0317 } 0318 } 0319 0320 acc = acc.trimmed(); 0321 if (!acc.isEmpty()) { 0322 tokens << acc; 0323 } 0324 0325 return tokens; 0326 } 0327 0328 SyntaxTree::Options SyntaxTree::parseOptions(const QStringList &args) 0329 { 0330 Options result; 0331 auto it = args.constBegin(); 0332 for (;it != args.constEnd(); it++) { 0333 if (it->startsWith("--")) { 0334 QString option = it->mid(2); 0335 QStringList list; 0336 it++; 0337 for (;it != args.constEnd(); it++) { 0338 if (it->startsWith("--")) { 0339 it--; 0340 break; 0341 } 0342 list << *it; 0343 } 0344 result.options.insert(option, list); 0345 if (it == args.constEnd()) { 0346 break; 0347 } 0348 } else { 0349 result.positionalArguments << *it; 0350 } 0351 } 0352 return result; 0353 }