diff --git a/src/commands.hxx b/src/commands.hxx index 35ebb29..a4888c2 100644 --- a/src/commands.hxx +++ b/src/commands.hxx @@ -32,6 +32,7 @@ class Script; class Command; +class Function; class Logger { public: @@ -121,7 +122,20 @@ class Command: public QObject { while (QTime::currentTime()1&&value.at(0)==value.at(value.size()-1) + ?value.at(0).toLatin1():'\0') { + case '"': case '\'': { + return value.mid(1, value.size()-2) + .split(QRegularExpression(QString(value[0])+", *" + +QString(value[0]))); + } break; + default: { + return value.split(QRegularExpression(", *")); + } + } + } static QWebElement find(QWebFrame* frame, QString selector, int repeat = 2, int sleepsec = 1) { QWebElement element; @@ -418,6 +432,7 @@ class Script: public QObject { reset(); _variables.clear(); _rvariables.clear(); + _functions.clear(); _timeout = 20; _clicktype = JAVASCRIPT_CLICK; } @@ -609,10 +624,29 @@ class Script: public QObject { _variables[name] = value; _rvariables[value] = name; } + void set(const Script& o) { + _variables = o._variables; + _rvariables = o._rvariables; + _timeout = o._timeout; + _clicktype = o._clicktype; + _cout.clear(); + _cerr.clear(); + _ignoreSignalsUntil.clear(); + _functions.clear(); + } void unset(QString name) { _rvariables.remove(_variables[name]); _variables.remove(name); } + void function(QString name, std::shared_ptr f) { + _functions[name] = f; + } + std::shared_ptr function(QString name) { + QMap >::iterator + it(_functions.find(name)); + if (it==_functions.end()) throw FunctionNotFound(name); + return *it; + } void timeout(int t) { _timeout = t; } @@ -637,24 +671,6 @@ class Script: public QObject { } return txt; } - public Q_SLOTS: - void log(QString text) { - text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text; - logging(text); - std::cout< unknown(QString line) { - if (!line.size()) return std::shared_ptr(new Empty()); - if (line[0]=='#') return std::shared_ptr(new Comment(line)); - throw UnknownCommand(line); // error - } void addSignals(QWebFrame* frame) { connect(dynamic_cast (frame->page()->networkAccessManager()), @@ -703,6 +719,24 @@ class Script: public QObject { this, SLOT(urlChanged(const QUrl&))); disconnect(&_timer, SIGNAL(timeout()), this, SLOT(timeout())); } + public Q_SLOTS: + void log(QString text) { + text = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss ")+text; + logging(text); + std::cout< unknown(QString line) { + if (!line.size()) return std::shared_ptr(new Empty()); + if (line[0]=='#') return std::shared_ptr(new Comment(line)); + throw UnknownCommand(line); // error + } void initPrototypes(); void add(Command* c) { _prototypes[c->tag()] = std::shared_ptr(c); @@ -774,6 +808,7 @@ class Script: public QObject { QString _ignoreSignalsUntil; QMap _variables; ///< variable mapping QMap _rvariables; ///< reverse variable mapping + QMap > _functions; int _timeout; ClickType _clicktype; }; @@ -1217,7 +1252,7 @@ class Execute: public Command { } QString description() const { return - tag()+" \n \n \n <...>" + tag()+" \n \n \n <...>" "\n\n" "Execute . The command can have space separated arguments. " "Following lines that are intended by at least " @@ -1416,7 +1451,8 @@ class Set: public Command { "Sets the value of a variable either to a constant, or to the output" " of a command. A command should be a command that produces an" " output, such as «do», which returns the result of JavaScript or" - " «execute», which returns the output of the executed command."; + " «execute», which returns the output of the executed command." + " All variables are global with regrad to functions."; } QString command() const { if (_next) @@ -1663,7 +1699,7 @@ class SetValue: public Command { QString description() const { return tag()+" -> ''\n"+ - tag()+" -> '', '', ..." + tag()+" -> '', '', <...>" "\n\n" "Set the selected element to a given value. It is mostly the same as" " the following constuct, except that options in a select are evaluated" @@ -1697,18 +1733,7 @@ class SetValue: public Command { QString value(script->replacevars(_value)); if (element.tagName()=="SELECT") { // value is a comma seperated list of option values - QStringList values; - switch (value.size()>1&&value.at(0)==value.at(value.size()-1) - ?value.at(0).toLatin1():'\0') { - case '"': case '\'': { - values = value.mid(1, value.size()-2) - .split(QRegularExpression(QString(value[0])+", *" - +QString(value[0]))); - } break; - default: { - values = value.split(QRegularExpression(", *")); - } - } + QStringList values(commaSeparatedList(value)); Q_FOREACH(QWebElement option, element.findAll("option")) { QString name(option.evaluateJavaScript("this.value").toString()); option.evaluateJavaScript @@ -1730,6 +1755,120 @@ class SetValue: public Command { QString _value; }; +class Function: public Command { + public: + QString tag() const { + return "function"; + } + QString description() const { + return + tag()+" [, , <...>]\n" + " \n" + " \n" + " <...>\n" + "\n\n" + "Define a function with arguments. The arguments are treated like" + " local variables. In a sequence of scripts within the same testrun," + " functions are inherited from all followin scripts, so you can first" + " load a script file that contains all functions. Within the same file," + " a function can be called before the definition.\n\n" + "If you quote the values, then quote all values with the same" + " quotes. If you need a comma within a value, you must quote."; + } + QString command() const { + return tag()+" "+_name+" "+_vars.join(" "); + } + std::shared_ptr parse(Script* script, QString args, + QStringList& in, int) { + std::shared_ptr cmd(new Function()); + if (!args.size()) throw BadArgument(tag()+" requires a "); + QStringList allargs(args.split(" ")); + cmd->_name = allargs.takeFirst().trimmed(); + cmd->_vars = commaSeparatedList(allargs.join(' ')); + script->function(cmd->_name, cmd); + QStringList commands; + int pos(-1); + while (in.size() && in[0].size() && in[0][0]==' ') { + if (pos<0) pos=in[0].toStdString().find_first_not_of(' '); + commands += in.takeFirst().mid(pos); + } + cmd->_script = std::shared_ptr